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

Commit

Permalink
[new-rule] newline-per-chained-call (#3278)
Browse files Browse the repository at this point in the history
  • Loading branch information
aervin_ authored and adidahiya committed Jan 10, 2018
1 parent ef28083 commit 4117f16
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export const rules = {
"match-default-export-name": true,
"new-parens": true,
"newline-before-return": true,
"newline-per-chained-call": true,
"no-angle-bracket-type-assertion": true,
"no-boolean-literal-compare": true,
"no-consecutive-blank-lines": true,
Expand Down
84 changes: 84 additions & 0 deletions src/rules/newlinePerChainedCallRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @license
* Copyright 2017 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
isCallExpression,
isElementAccessExpression,
isPropertyAccessExpression,
isSameLine,
} from "tsutils";
import * as ts from "typescript";
import * as Lint from "..";

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "newline-per-chained-call",
description: Lint.Utils.dedent`
Requires that chained method calls be broken apart onto separate lines.`,
rationale: Lint.Utils.dedent`
This style helps to keep code 'vertical', avoiding the need for side-scrolling in IDEs or text editors.`,
optionsDescription: "Not configurable",
options: null,
type: "style",
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */
public static FAILURE_STRING = "When chaining calls, put method calls on new lines.";

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

class NewlinePerChainedCallWalker extends Lint.AbstractWalker<void> {
public walk(sourceFile: ts.SourceFile) {
const checkForSameLine = (node: ts.Node): void => {
if (
isCallExpression(node) &&
isPropertyAccessExpression(node.expression) &&
isSameLine(
sourceFile,
node.expression.expression.end,
node.expression.name.pos,
) &&
hasChildCall(node.expression)
) {
this.addFailure(
node.expression.name.pos - 1,
node.expression.name.end,
Rule.FAILURE_STRING,
);
}
return ts.forEachChild(node, checkForSameLine);
};
return ts.forEachChild(sourceFile, checkForSameLine);
}
}

function hasChildCall(node: ts.PropertyAccessExpression): boolean {
let { expression } = node;
while (
isPropertyAccessExpression(expression) ||
isElementAccessExpression(expression)
) {
({ expression } = expression);
}
return expression.kind === ts.SyntaxKind.CallExpression;
}
120 changes: 120 additions & 0 deletions test/rules/newline-per-chained-call/default/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
this.getFoo()[0].toString();
~~~~~~~~~ [ERROR]

this.foo()["bar"]().buzz();
~~~~~ [ERROR]

this.foo()["bar"]();

foo().bar();
~~~~ [ERROR]

const y: string[] = _observable
.map(function(item) { return item.helloYay().another() }
~~~~~~~~ [ERROR]
.operator()
.another(function(result) { return result.hello.Yay! }).wrong();
~~~~~~ [ERROR]

SomeClass.propA.helloYay((a: number) => {
return a + 1;
});

this.some.nested();

const y: string[] = _observable
.map(function(item) { return item.helloYay! })
.operator()
.another(function(result) { return result.hello.Yay! });


const y: string[] = _observable.map(item => item.helloYay).operator().another(function(result) { return result.helloYay! });
~~~~~~~~ [ERROR]
~~~~~~~~~ [ERROR]

const x: string[] = _observable.map(item => item.helloYay);

SomeClass.propA.propB.helloYay();

SomeClass
.propA
.propB
.helloYay();

SomeClass
.propA
.propB.helloYay();

SomeClass
.propA
.propB
.helloYay(function() {
return 1;
}).test();
~~~~~ [ERROR]

SomeClass
.propA
.propB
.helloYay(function() {
return 1;
}). test();
~~~~~~~ [ERROR]

SomeClass
.propA
.propB
.helloYay(function() {
return 1;
}).
~
test();
~~~~~~~~ [ERROR]

SomeClass.propA.propB.methodB(() => {
return "hello Yay!";
}).helloYay((a: number) => {
~~~~~~~~~ [ERROR]
return a + 1;
});

SomeClass.propA.propB.methodB(() => {
return "hello Yay!";
})
.helloYay((a: number) => {
return a + 1;
});

SomeClass.propA.propB.methodB(() => {
return "hello Yay!";
})
.helloYay((a: number) => {
return obj.method()
.chainedButOkay(
objB.nested().superNestedCall()
~~~~~~~~~~~~~~~~ [ERROR]
)
});

SomeClass.propA.propB.methodB(() => {
return "hello Yay!";
})
.helloYay((a: number) => {
return obj.method()
.chainedButOkay(
objB.nested()
.superNestedCall()
)
});

SomeClass
.propA
.propB
.methodC(() => {
return "hello Yay!";
})
.helloYay(() => {
return 1;
});

[ERROR]: When chaining calls, put method calls on new lines.
5 changes: 5 additions & 0 deletions test/rules/newline-per-chained-call/default/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"newline-per-chained-call": true
}
}
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"static-before-instance",
"variables-before-functions"
],
"newline-per-chained-call": false,
"no-console": {
"options": ["log"]
},
Expand Down

0 comments on commit 4117f16

Please sign in to comment.