From 64fa1176ec483f7990666c1de542644c4393ff49 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Thu, 13 Dec 2018 14:57:35 +0900 Subject: [PATCH 1/2] Feature: Introduce new rule: newline-before-throw --- src/configs/all.ts | 1 + src/rules/newlineBeforeThrowRule.ts | 86 +++++++++++++ .../newline-before-throw/default/test.ts.lint | 114 ++++++++++++++++++ .../newline-before-throw/default/tslint.json | 5 + 4 files changed, 206 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/configs/all.ts b/src/configs/all.ts index bb75db71b73..b310c58ba4c 100644 --- a/src/configs/all.ts +++ b/src/configs/all.ts @@ -201,6 +201,7 @@ export const rules = { "match-default-export-name": true, "new-parens": true, "newline-before-return": true, + "newline-before-throw": true, "newline-per-chained-call": true, "no-angle-bracket-type-assertion": true, "no-boolean-literal-compare": true, 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 + } +} From dd7368d4f2ebc42ee7339e69e8fa7a349cee4dbc Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Thu, 13 Dec 2018 15:00:58 +0900 Subject: [PATCH 2/2] Fix: Style with new rule: newline-before-throw --- src/configuration.ts | 1 + src/linter.ts | 1 + src/rules/memberOrderingRule.ts | 1 + src/runner.ts | 1 + test/utils.ts | 1 + 5 files changed, 5 insertions(+) diff --git a/src/configuration.ts b/src/configuration.ts index 457de0f4556..a253d5b2630 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -271,6 +271,7 @@ export function readConfigurationFile(filepath: string): RawConfigFile { } } catch (e) { const error = e as Error; + // include the configuration file being parsed in the error since it may differ from the directly referenced config throw new Error(`${error.message} in ${filepath}`); } diff --git a/src/linter.ts b/src/linter.ts index e836dae216a..97cb9dc5ded 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -312,6 +312,7 @@ export class Linter { const INVALID_SOURCE_ERROR = dedent` Invalid source file: ${fileName}. Ensure that the files supplied to lint have a .ts, .tsx, .d.ts, .js or .jsx extension. `; + throw new FatalError(INVALID_SOURCE_ERROR); } return sourceFile; diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index 55682fd7911..9139fc10b34 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -395,6 +395,7 @@ class MemberOrderingWalker extends Lint.AbstractWalker { return name; } } + throw new Error("Expected to find a name"); } diff --git a/src/runner.ts b/src/runner.ts index a6f7a63d8e9..fd4ad91079a 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -131,6 +131,7 @@ export async function run(options: Options, logger: Logger): Promise { logger.error(`${error.message}\n`); return Status.FatalError; } + throw error; } } diff --git a/test/utils.ts b/test/utils.ts index 33bf58f94dc..4eb89e471ca 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -43,5 +43,6 @@ export function createTempFile(extension: string) { return attempt; } } + throw new Error("Couldn't create temp file"); }