From 600f81f18137d6891c7137d026fd99b48d5228eb Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Thu, 13 Dec 2018 14:54:21 +0900 Subject: [PATCH] Feature: Introduce new rule: newline-before-throw --- src/rules/newlineBeforeThrowRule.ts | 86 +++++++++++++ .../newline-before-throw/default/test.ts.lint | 114 ++++++++++++++++++ .../newline-before-throw/default/tslint.json | 5 + 3 files changed, 205 insertions(+) create mode 100644 src/rules/newlineBeforeThrowRule.ts create mode 100644 test/rules/newline-before-throw/default/test.ts.lint create mode 100644 test/rules/newline-before-throw/default/tslint.json diff --git a/src/rules/newlineBeforeThrowRule.ts b/src/rules/newlineBeforeThrowRule.ts new file mode 100644 index 00000000000..8ae69e46077 --- /dev/null +++ b/src/rules/newlineBeforeThrowRule.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright 2018 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 { getPreviousStatement } from "tsutils"; +import * as ts from "typescript"; +import * as Lint from "../index"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "newline-before-throw", + description: "Enforces blank line before throw when not the only line in the block.", + rationale: "Helps maintain a readable style in your codebase.", + optionsDescription: "Not configurable.", + options: {}, + optionExamples: [true], + type: "style", + typescriptOnly: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "Missing blank line before throw"; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker( + new NewlineBeforeThrowWalker(sourceFile, this.ruleName, undefined), + ); + } +} + +class NewlineBeforeThrowWalker extends Lint.AbstractWalker { + public walk(sourceFile: ts.SourceFile) { + const cb = (node: ts.Node): void => { + if (node.kind === ts.SyntaxKind.ThrowStatement) { + this.visitThrowStatement(node as ts.ThrowStatement); + } + return ts.forEachChild(node, cb); + }; + return ts.forEachChild(sourceFile, cb); + } + + private visitThrowStatement(node: ts.ThrowStatement) { + const prev = getPreviousStatement(node); + if (prev === undefined) { + // throw is not within a block (e.g. the only child of an IfStatement) or the first statement of the block + // no need to check for preceding newline + return; + } + + let start = node.getStart(this.sourceFile); + let line = ts.getLineAndCharacterOfPosition(this.sourceFile, start).line; + const comments = ts.getLeadingCommentRanges(this.sourceFile.text, node.pos); + if (comments !== undefined) { + // check for blank lines between comments + for (let i = comments.length - 1; i >= 0; --i) { + const endLine = ts.getLineAndCharacterOfPosition(this.sourceFile, comments[i].end) + .line; + if (endLine < line - 1) { + return; + } + start = comments[i].pos; + line = ts.getLineAndCharacterOfPosition(this.sourceFile, start).line; + } + } + const prevLine = ts.getLineAndCharacterOfPosition(this.sourceFile, prev.end).line; + + if (prevLine >= line - 1) { + // Previous statement is on the same or previous line + this.addFailure(start, start, Rule.FAILURE_STRING); + } + } +} diff --git a/test/rules/newline-before-throw/default/test.ts.lint b/test/rules/newline-before-throw/default/test.ts.lint new file mode 100644 index 00000000000..7f314624f41 --- /dev/null +++ b/test/rules/newline-before-throw/default/test.ts.lint @@ -0,0 +1,114 @@ +function foo(bar) { + if (!bar) { + throw new Error(); + } + throw new Error(); + ~nil [0] +} + +function foo(bar) { + if (!bar) { + var e = new Error(); + throw e; + ~nil [0] + } + + throw bar; +} + +function foo(bar) { + if (!bar) { + throw new Error(); + } + /* multi-line + ~nil [0] + comment */ + throw new Error(); +} + +var fn = () => null; +function foo() { + fn(); + throw new Error(); + ~nil [0] +} + +function foo(fn) { + fn(); throw new Error(); + ~nil [0] +} + +function foo() { + throw new Error(); +} + +function foo() { + + throw new Error(); +} + +function foo(bar) { + if (!bar) throw new Error(); +} + +function foo(bar) { + let someCall; + if (!bar) throw new Error(); +} + +function foo(bar) { + if (!bar) { throw new Error() }; +} + +function foo(bar) { + if (!bar) { + throw new Error(); + } +} + +function foo(bar) { + if (!bar) { + throw new Error(); + } + + throw bar; +} + +function foo(bar) { + if (!bar) { + + throw new Error(); + } +} + +function foo() { + + // comment + throw new Error(); +} + +function test() { + console.log("Any statement"); + // Any comment + + throw new Error(); +} + +function foo() { + fn(); + // comment + + // comment + throw new Error(); +} + +function bar() { + "some statement"; + //comment + ~nil [0] + //comment + //comment + throw new Error(); +} + +[0]: Missing blank line before throw diff --git a/test/rules/newline-before-throw/default/tslint.json b/test/rules/newline-before-throw/default/tslint.json new file mode 100644 index 00000000000..58078627556 --- /dev/null +++ b/test/rules/newline-before-throw/default/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "newline-before-throw": true + } +}