Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ This is a tslint rule that warns about focussed Jasmine tests - fdescribe and fi
(defocus) app.ts[8, 5]: Calls to 'fit' are not allowed.
```

You can add the `deexclude` parameter to warn also for `xdescribe` and `xit`:
```
"rules": {
"defocus": [true, "deexclude"],
...
```

## Dependencies

Version 2.0.x of this rule requires version 5.x of tslint.
Expand Down
66 changes: 50 additions & 16 deletions src/defocusRule.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,74 @@
import * as Lint from "tslint";
import * as ts from "typescript";

// tslint:disable-next-line:interface-name
interface Options {
deexclude: boolean;
}

export class Rule extends Lint.Rules.AbstractRule {

public static metadata: Lint.IRuleMetadata = {
/* tslint:disable:object-literal-sort-keys */
ruleName: "defocus",
description: "Bans the use of `fdescribe` and 'fit' Jasmine functions.",
description: "Bans the use of `fdescribe` and `fit` Jasmine functions.",
rationale: "It is all too easy to mistakenly commit a focussed Jasmine test suite or spec.",
options: null,
optionsDescription: "Not configurable.",
options: {
type: "array",
items: [
{
type: "string",
enum: ["deexclude"],
},
],
additionalItems: false,
},
optionExamples: [
[true],
[true, "deexclude"],
],
optionsDescription: "A `deexclude` argument can be added to bans the use of `xdescribe` and `xit`",
type: "functionality",
typescriptOnly: false,
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
// return this.applyWithFunction(sourceFile, walk);
return this.applyWithWalker(new DefocusWalker(sourceFile, this.ruleName, {
deexclude: this.ruleArguments.indexOf("deexclude") !== -1,
}));
}
}

function walk(ctx: Lint.WalkContext<void>) {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.CallExpression) {
const expression = (node as ts.CallExpression).expression;
const functionName = expression.getText();
bannedFunctions.forEach((banned) => {
if (banned === functionName) {
ctx.addFailureAtNode(expression, failureMessage(functionName));
// tslint:disable-next-line:max-classes-per-file
class DefocusWalker extends Lint.AbstractWalker<Options> {
public walk(sourceFile: ts.SourceFile): void {

const cb = (node: ts.Node): void => {
if (node.kind === ts.SyntaxKind.CallExpression) {
const expression = (node as ts.CallExpression).expression;
const functionName = expression.getText();
bannedFunctions.forEach((banned) => {
if (banned === functionName) {
this.addFailureAtNode(expression, failureMessage(functionName));
}
});
if (this.options.deexclude) {
bannedExcludeFunctions.forEach((banned) => {
if (banned === functionName) {
this.addFailureAtNode(expression, failureMessage(functionName));
}
});
}
});
}
return ts.forEachChild(node, cb);
});
}
return ts.forEachChild(node, cb);
};
return ts.forEachChild(sourceFile, cb);
}
}

const bannedFunctions: ReadonlyArray<string> = ["fdescribe", "fit"];
const bannedExcludeFunctions: ReadonlyArray<string> = ["xdescribe", "xit"];

const failureMessage = (functionName: string) => {
return `Calls to '${functionName}' are not allowed.`;
Expand Down
17 changes: 17 additions & 0 deletions src/test/rules/defocus/test.ts.lint
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,24 @@ fdescribe(() => {
~~~ [Calls to 'fit' are not allowed.]
});

xdescribe(() => {
~~~~~~~~~ [Calls to 'xdescribe' are not allowed.]
xit(() => {});
~~~ [Calls to 'xit' are not allowed.]
});

describe(() => {
fdescribe(() => {
~~~~~~~~~ [Calls to 'fdescribe' are not allowed.]
});
});

describe(() => {
xdescribe(() => {
~~~~~~~~~ [Calls to 'xdescribe' are not allowed.]
});
});

// in a comment, so ok: fdescibe, fit()

fdescribe(function(){});
Expand All @@ -33,13 +45,18 @@ abcdefdescribe(function(){

function(){
console.log('fit', 'function');
console.log('xit', 'function');
}

export class Test {
constructor() {
this.fdescribe();
this.xdescribe();
}

public fdescribe(): void {
}

public xdescribe(): void {
}
}
5 changes: 4 additions & 1 deletion src/test/rules/defocus/tslint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"rulesDirectory": "../../../../dist",
"rules": {
"defocus": true
"defocus": [
true,
"deexclude"
]
}
}
36 changes: 34 additions & 2 deletions src/test/specs/defocusRule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const ruleName = 'defocus';

const ruleOptions: Readonly<Lint.IOptions> = {
disabledIntervals: [],
ruleArguments: [],
ruleArguments: ["deexclude"],
ruleName,
ruleSeverity: "error"
ruleSeverity: "error",
};

describe(ruleName, function test() {
Expand All @@ -24,13 +24,27 @@ describe(ruleName, function test() {
expect(failures[0].getFailure()).eq('Calls to \'fdescribe\' are not allowed.');
});

it('should fail when "xdescribe" is called', () => {
const sourceFile = getSourceFile('shouldFailWhenXDescribeCalled.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(1);
expect(failures[0].getFailure()).eq('Calls to \'xdescribe\' are not allowed.');
});

it('should fail when "fit" is called', () => {
const sourceFile = getSourceFile('shouldFailWhenFitCalled.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(1);
expect(failures[0].getFailure()).eq('Calls to \'fit\' are not allowed.');
});

it('should fail when "xit" is called', () => {
const sourceFile = getSourceFile('shouldFailWhenXitCalled.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(1);
expect(failures[0].getFailure()).eq('Calls to \'xit\' are not allowed.');
});

it('should not fail for a snippet without "fit" or "fdescribe"', () => {
const sourceFile = getSourceFile('shouldPassWithoutFitOrFDescribe.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
Expand All @@ -43,17 +57,35 @@ describe(ruleName, function test() {
expect(failures).length(0);
});

it('should not flag a false alert if "xdescribe" appears as a variable name', () => {
const sourceFile = getSourceFile('allowsXDescribeAsVariableName.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(0);
});

it('should not flag a false alert if "fit" appears as a object property key or value', () => {
const sourceFile = getSourceFile('allowsFitAsPropKeyValue.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(0);
});

it('should not flag a false alert if "xit" appears as a object property key or value', () => {
const sourceFile = getSourceFile('allowsXitAsPropKeyValue.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(0);
});

it('should not flag a false alert if "fdescribe" or "fit" appear as function parameters', () => {
const sourceFile = getSourceFile('allowsFDescribeFitAsFunctionParams.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(0);
});

it('should not flag a false alert if "xdescribe" or "xit" appear as function parameters', () => {
const sourceFile = getSourceFile('allowsXDescribeXitAsFunctionParams.ts');
const failures = new Rule(ruleOptions).apply(sourceFile);
expect(failures).length(0);
});
});

function getSourceFile(fileName: string): ts.SourceFile {
Expand Down
1 change: 1 addition & 0 deletions src/test/specs/snippets/allowsXDescribeAsVariableName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const xdescribe = {test: 123}
3 changes: 3 additions & 0 deletions src/test/specs/snippets/allowsXDescribeXitAsFunctionParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function (xdescribe, xit) {
console.log(xdescribe, xit);
}();
1 change: 1 addition & 0 deletions src/test/specs/snippets/allowsXitAsPropKeyValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let testObject = {xit: "test", test: "xit"}
3 changes: 3 additions & 0 deletions src/test/specs/snippets/shouldFailWhenXDescribeCalled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
xdescribe(() => {
console.log("testing")
});
5 changes: 5 additions & 0 deletions src/test/specs/snippets/shouldFailWhenXitCalled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe(() => {
xit(() => {
console.log("testing")
});
});