Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Added space-before-function-paren rule
Browse files Browse the repository at this point in the history
  • Loading branch information
cameron-mcateer committed Dec 19, 2016
1 parent 501d4c0 commit 936078a
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 0 deletions.
167 changes: 167 additions & 0 deletions src/rules/spaceBeforeFunctionParenRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import * as ts from "typescript";
import * as Lint from "../index";

const ALWAYS_OR_NEVER = {
enum: ["always", "never"],
type: "string",
};

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
description: "Require or disallow a space before function parenthesis",
optionExamples: [`[true, {"anonymous": "always", "named": "never", "asyncArrow": "always"}]`],
options: {
properties: {
anonymous: ALWAYS_OR_NEVER,
asyncArrow: ALWAYS_OR_NEVER,
constructor: ALWAYS_OR_NEVER,
method: ALWAYS_OR_NEVER,
named: ALWAYS_OR_NEVER,
},
type: "object",
},
optionsDescription: Lint.Utils.dedent`
One argument which is an object which may contain the keys \`anonymous\`, \`named\`, and \`asyncArrow\`
These should be set to either \`"always"\` or \`"never"\`.
* \`"anonymous"\` checks before the opening paren in anonymous functions
* \`"named"\` checks before the opening paren in named functions
* \`"asyncArrow"\` checks before the opening paren in async arrow functions,
* \`"method"\` checks before the opening paren in class methods
*\`"constructor"\` checks before the opening paren in class constructors
`,
ruleName: "space-before-function-paren",
type: "style",
typescriptOnly: false,
};
public static INVALID_WHITESPACE_ERROR = "Spaces before function parens are disallowed";
public static MISSING_WHITESPACE_ERROR = "Missing whitespace before function parens";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new FunctionWalker(sourceFile, this.getOptions()));
}
}

class FunctionWalker extends Lint.RuleWalker {
private scanner: ts.Scanner;

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);
this.scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, sourceFile.text);
}

protected visitArrowFunction(node: ts.FunctionLikeDeclaration): void {
this.visitFunction(node);
super.visitArrowFunction(node);
}

public visitConstructorDeclaration(node: ts.ConstructorDeclaration): void {
this.visitFunction(node);
super.visitConstructorDeclaration(node);
}

protected visitFunctionDeclaration(node: ts.FunctionDeclaration): void {
this.visitFunction(node);
super.visitFunctionDeclaration(node);
}

protected visitFunctionExpression(node: ts.FunctionExpression): void {
this.visitFunction(node);
super.visitFunctionExpression(node);
}

public visitMethodDeclaration(node: ts.MethodDeclaration): void {
this.visitFunction(node);
super.visitMethodDeclaration(node);
}

public visitMethodSignature(node: ts.SignatureDeclaration): void {
this.visitFunction(node);
super.visitMethodSignature(node);
}

private visitFunction(node: ts.Node): void {
let identifier: ts.Node;
let openParen: ts.Node;
let isAsyncArrow = false;

const isArrow = node.kind === ts.SyntaxKind.ArrowFunction;

node.getChildren().forEach((child: ts.Node): void => {
switch (child.kind) {
case ts.SyntaxKind.SyntaxList:
if (isArrow && child.getStart() === node.getStart() && child.getText() === "async") {
isAsyncArrow = true;
}
break;

case ts.SyntaxKind.Identifier:
case ts.SyntaxKind.ConstructorKeyword:
identifier = child;
break;

case ts.SyntaxKind.OpenParenToken:
openParen = child;
break;

default: break;
}
});

if (openParen === undefined || (isArrow && !isAsyncArrow)) {
return;
}

const hasIdentifier = identifier !== undefined && (identifier.getEnd() !== identifier.getStart());
let optionName;

if (isAsyncArrow) {
optionName = "asyncArrow";
} else {
switch (node.kind) {
case ts.SyntaxKind.Constructor:
optionName = "constructor";
break;

case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.MethodSignature:
optionName = "method";
break;

default:
optionName = hasIdentifier ? "named" : "anonymous";
}
}

const option = this.getOption(optionName);
this.scanner.setTextPos(openParen.getStart() - 1);
const prevTokenKind = this.scanner.scan();
const hasSpace = prevTokenKind === ts.SyntaxKind.WhitespaceTrivia;
let failure: Lint.RuleFailure;

if (hasSpace && option === "never") {
const fix = new Lint.Fix(Rule.metadata.ruleName, [
this.deleteText(openParen.getStart() - 1, 1),
]);
failure = this.createFailure(openParen.getStart(), 1, Rule.INVALID_WHITESPACE_ERROR, fix);
} else if (!hasSpace && option === "always") {
const fix = new Lint.Fix(Rule.metadata.ruleName, [
this.appendText(openParen.getStart(), " "),
]);
failure = this.createFailure(openParen.getStart(), 1, Rule.MISSING_WHITESPACE_ERROR, fix);
}

if (failure !== undefined) {
this.addFailure(failure);
}
}

private getOption(option: string): string {
const allOptions = this.getOptions();
if (allOptions === null || allOptions.length === 0) {
return undefined;
}

return allOptions[0][option];
}
}
36 changes: 36 additions & 0 deletions test/rules/space-before-function-paren/always/test.js.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Anonymous
function() {}
~ [space-before-function-paren]
function(): void {
~ [space-before-function-paren]
}
function(a: string, cb: ()=>{}): void {}
~ [space-before-function-paren]

var f = function() {};
~ [space-before-function-paren]
var f = function(): void {
~ [space-before-function-paren]
};
var f = function(a: string, cb: ()=>{}): void {};
~ [space-before-function-paren]


// Named
function foobar(){}
~ [space-before-function-paren]
function foobar(): void{
~ [space-before-function-paren]
}
function foobar(a: string, cb: ()=>{}): void{}
~ [space-before-function-paren]

var f = function foobar(){};
~ [space-before-function-paren]
var f = function foobar(): void{
~ [space-before-function-paren]
};
var f = function foobar(a: string, cb: ()=>{}): void{};
~ [space-before-function-paren]

[space-before-function-paren]: Missing whitespace before function parens
68 changes: 68 additions & 0 deletions test/rules/space-before-function-paren/always/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Anonymous
function () {}
function (): void {
}
function (a: string, cb: ()=>{}): void {}

var f = function () {};
var f = function (): void {
};
var f = function (a: string, cb: ()=>{}): void {};


// Named
function foobar (){}
function foobar (): void{
}
function foobar (a: string, cb: ()=>{}): void{}

var f = function foobar (){};
var f = function foobar (): void{
};
var f = function foobar (a: string, cb: ()=>{}): void{};


// Async Arrow
// ignore
() => {};
var arrow = () => {};

async () => {};
var arrow = async () => {};


// Method
interface IMyInterface {
one ();
two (): void;
three (a: string, cb: ()=>{}): void;
}
class MyClass {
one () {}
two (): void {
}
three (a: string, cb: ()=>{}): void {}
}


// Constructor
interface IMyInterface {
constructor ();
}
class MyClass {
constructor () {}
}
interface IMyInterface {
constructor (): void;
}
class MyClass {
constructor (): void {
}
}
interface IMyInterface {
constructor (a: string, cb: ()=>{}): void;
}
class MyClass {
constructor (a: string, cb: ()=>{}): void {}
}

95 changes: 95 additions & 0 deletions test/rules/space-before-function-paren/always/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Anonymous
function() {}
~ [space-before-function-paren]
function(): void {
~ [space-before-function-paren]
}
function(a: string, cb: ()=>{}): void {}
~ [space-before-function-paren]

var f = function() {};
~ [space-before-function-paren]
var f = function(): void {
~ [space-before-function-paren]
};
var f = function(a: string, cb: ()=>{}): void {};
~ [space-before-function-paren]


// Named
function foobar(){}
~ [space-before-function-paren]
function foobar(): void{
~ [space-before-function-paren]
}
function foobar(a: string, cb: ()=>{}): void{}
~ [space-before-function-paren]

var f = function foobar(){};
~ [space-before-function-paren]
var f = function foobar(): void{
~ [space-before-function-paren]
};
var f = function foobar(a: string, cb: ()=>{}): void{};
~ [space-before-function-paren]


// Async Arrow
// ignore
() => {};
var arrow = () => {};

async() => {};
~ [space-before-function-paren]
var arrow = async() => {};
~ [space-before-function-paren]


// Method
interface IMyInterface {
one();
~ [space-before-function-paren]
two(): void;
~ [space-before-function-paren]
three(a: string, cb: ()=>{}): void;
~ [space-before-function-paren]
}
class MyClass {
one() {}
~ [space-before-function-paren]
two(): void {
~ [space-before-function-paren]
}
three(a: string, cb: ()=>{}): void {}
~ [space-before-function-paren]
}


// Constructor
interface IMyInterface {
constructor();
~ [space-before-function-paren]
}
class MyClass {
constructor() {}
~ [space-before-function-paren]
}
interface IMyInterface {
constructor(): void;
~ [space-before-function-paren]
}
class MyClass {
constructor(): void {
~ [space-before-function-paren]
}
}
interface IMyInterface {
constructor(a: string, cb: ()=>{}): void;
~ [space-before-function-paren]
}
class MyClass {
constructor(a: string, cb: ()=>{}): void {}
~ [space-before-function-paren]
}

[space-before-function-paren]: Missing whitespace before function parens
26 changes: 26 additions & 0 deletions test/rules/space-before-function-paren/always/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"rules": {
"space-before-function-paren": [
true,
{
"anonymous": "always",
"named": "always",
"asyncArrow": "always",
"method": "always",
"constructor": "always"
}
]
},
"jsRules": {
"space-before-function-paren": [
true,
{
"anonymous": "always",
"named": "always",
"asyncArrow": "always",
"method": "always",
"constructor": "always"
}
]
}
}

0 comments on commit 936078a

Please sign in to comment.