diff --git a/docs/rules/semicolon/index.html b/docs/rules/semicolon/index.html index 8b62432c4b1..095bfb1ee96 100644 --- a/docs/rules/semicolon/index.html +++ b/docs/rules/semicolon/index.html @@ -9,7 +9,6 @@ * `"never"` disallows semicolons at the end of every statement except for when they are necessary. The following arguments may be optionaly provided: - * `"ignore-interfaces"` skips checking semicolons at the end of interface members. * `"ignore-bound-class-methods"` skips checking semicolons at the end of bound class methods. options: @@ -52,4 +51,4 @@ ], "additionalItems": false } ---- +--- \ No newline at end of file diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 3d7f7aed9ec..242562560d3 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -17,7 +17,8 @@ import * as ts from "typescript"; -import {RuleWalker} from "../walker/ruleWalker"; +import {doesIntersect} from "../utils"; +import {IWalker} from "../walker"; import {IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure} from "./rule"; export abstract class AbstractRule implements IRule { @@ -36,7 +37,7 @@ export abstract class AbstractRule implements IRule { return false; } - constructor(ruleName: string, private value: any, disabledIntervals: IDisabledInterval[]) { + constructor(ruleName: string, private value: any, private disabledIntervals: IDisabledInterval[]) { let ruleArguments: any[] = []; if (Array.isArray(value) && value.length > 1) { @@ -56,12 +57,23 @@ export abstract class AbstractRule implements IRule { public abstract apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; - public applyWithWalker(walker: RuleWalker): RuleFailure[] { + public applyWithWalker(walker: IWalker): RuleFailure[] { walker.walk(walker.getSourceFile()); - return walker.getFailures(); + return this.filterFailures(walker.getFailures()); } public isEnabled(): boolean { return AbstractRule.isRuleEnabled(this.value); } + + protected filterFailures(failures: RuleFailure[]): RuleFailure[] { + const result: RuleFailure[] = []; + for (const failure of failures) { + // don't add failures for a rule if the failure intersects an interval where that rule is disabled + if (!doesIntersect(failure, this.disabledIntervals) && !result.some((f) => f.equals(failure))) { + result.push(failure); + } + } + return result; + } } diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 5e969b3710e..16ae16950d8 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import {RuleWalker} from "../walker/ruleWalker"; +import {IWalker} from "../walker"; export interface IRuleMetadata { /** @@ -96,7 +96,7 @@ export interface IRule { getOptions(): IOptions; isEnabled(): boolean; apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; - applyWithWalker(walker: RuleWalker): RuleFailure[]; + applyWithWalker(walker: IWalker): RuleFailure[]; } export class Replacement { diff --git a/src/language/utils.ts b/src/language/utils.ts index 6a17f363acb..826283e904b 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -196,3 +196,36 @@ export function unwrapParentheses(node: ts.Expression) { } return node; } + +export function isScopeBoundary(node: ts.Node): boolean { + return node.kind === ts.SyntaxKind.FunctionDeclaration + || node.kind === ts.SyntaxKind.FunctionExpression + || node.kind === ts.SyntaxKind.PropertyAssignment + || node.kind === ts.SyntaxKind.ShorthandPropertyAssignment + || node.kind === ts.SyntaxKind.MethodDeclaration + || node.kind === ts.SyntaxKind.Constructor + || node.kind === ts.SyntaxKind.ModuleDeclaration + || node.kind === ts.SyntaxKind.ArrowFunction + || node.kind === ts.SyntaxKind.ParenthesizedExpression + || node.kind === ts.SyntaxKind.ClassDeclaration + || node.kind === ts.SyntaxKind.ClassExpression + || node.kind === ts.SyntaxKind.InterfaceDeclaration + || node.kind === ts.SyntaxKind.GetAccessor + || node.kind === ts.SyntaxKind.SetAccessor + || node.kind === ts.SyntaxKind.SourceFile && ts.isExternalModule(node as ts.SourceFile); +} + +export function isBlockScopeBoundary(node: ts.Node): boolean { + return isScopeBoundary(node) + || node.kind === ts.SyntaxKind.Block + || node.kind === ts.SyntaxKind.DoStatement + || node.kind === ts.SyntaxKind.WhileStatement + || node.kind === ts.SyntaxKind.ForStatement + || node.kind === ts.SyntaxKind.ForInStatement + || node.kind === ts.SyntaxKind.ForOfStatement + || node.kind === ts.SyntaxKind.WithStatement + || node.kind === ts.SyntaxKind.SwitchStatement + || node.parent !== undefined + && (node.parent.kind === ts.SyntaxKind.TryStatement + || node.parent.kind === ts.SyntaxKind.IfStatement); +} diff --git a/src/language/walker/blockScopeAwareRuleWalker.ts b/src/language/walker/blockScopeAwareRuleWalker.ts index 75f862ee61a..185ab14e723 100644 --- a/src/language/walker/blockScopeAwareRuleWalker.ts +++ b/src/language/walker/blockScopeAwareRuleWalker.ts @@ -17,6 +17,7 @@ import * as ts from "typescript"; +import {isBlockScopeBoundary} from "../utils"; import {ScopeAwareRuleWalker} from "./scopeAwareRuleWalker"; /** @@ -30,7 +31,7 @@ export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalk super(sourceFile, options); // initialize with global scope if file is not a module - this.blockScopeStack = this.fileIsModule ? [] : [this.createBlockScope(sourceFile)]; + this.blockScopeStack = ts.isExternalModule(sourceFile) ? [] : [this.createBlockScope(sourceFile)]; } public abstract createBlockScope(node: ts.Node): U; @@ -85,20 +86,6 @@ export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalk } private isBlockScopeBoundary(node: ts.Node): boolean { - return super.isScopeBoundary(node) - || node.kind === ts.SyntaxKind.Block - || node.kind === ts.SyntaxKind.DoStatement - || node.kind === ts.SyntaxKind.WhileStatement - || node.kind === ts.SyntaxKind.ForStatement - || node.kind === ts.SyntaxKind.ForInStatement - || node.kind === ts.SyntaxKind.ForOfStatement - || node.kind === ts.SyntaxKind.WithStatement - || node.kind === ts.SyntaxKind.SwitchStatement - || isParentKind(node, ts.SyntaxKind.TryStatement) - || isParentKind(node, ts.SyntaxKind.IfStatement); + return isBlockScopeBoundary(node); } } - -function isParentKind(node: ts.Node, kind: ts.SyntaxKind) { - return node.parent != null && node.parent.kind === kind; -} diff --git a/src/language/walker/index.ts b/src/language/walker/index.ts index b762aa289ed..bf1ab5df58b 100644 --- a/src/language/walker/index.ts +++ b/src/language/walker/index.ts @@ -21,3 +21,4 @@ export * from "./ruleWalker"; export * from "./scopeAwareRuleWalker"; export * from "./skippableTokenAwareRuleWalker"; export * from "./syntaxWalker"; +export * from "./walker"; diff --git a/src/language/walker/ruleWalker.ts b/src/language/walker/ruleWalker.ts index 0f98f2e41cc..bc43d53c058 100644 --- a/src/language/walker/ruleWalker.ts +++ b/src/language/walker/ruleWalker.ts @@ -17,26 +17,22 @@ import * as ts from "typescript"; -import {Fix, IDisabledInterval, IOptions, Replacement, RuleFailure} from "../rule/rule"; -import {doesIntersect} from "../utils"; +import {Fix, IOptions, Replacement, RuleFailure} from "../rule/rule"; import {SyntaxWalker} from "./syntaxWalker"; +import {IWalker} from "./walker"; -export class RuleWalker extends SyntaxWalker { +export class RuleWalker extends SyntaxWalker implements IWalker { private limit: number; - private position: number; private options?: any[]; private failures: RuleFailure[]; - private disabledIntervals: IDisabledInterval[]; private ruleName: string; constructor(private sourceFile: ts.SourceFile, options: IOptions) { super(); - this.position = 0; this.failures = []; this.options = options.ruleArguments; this.limit = this.sourceFile.getFullWidth(); - this.disabledIntervals = options.disabledIntervals; this.ruleName = options.ruleName; } @@ -68,8 +64,8 @@ export class RuleWalker extends SyntaxWalker { } } - public skip(node: ts.Node) { - this.position += node.getFullWidth(); + public skip(_node: ts.Node) { + return; // TODO remove this method in next major version } public createFailure(start: number, width: number, failure: string, fix?: Fix): RuleFailure { @@ -79,10 +75,7 @@ export class RuleWalker extends SyntaxWalker { } public addFailure(failure: RuleFailure) { - // don't add failures for a rule if the failure intersects an interval where that rule is disabled - if (!this.existsFailure(failure) && !doesIntersect(failure, this.disabledIntervals)) { - this.failures.push(failure); - } + this.failures.push(failure); } /** Add a failure with any arbitrary span. Prefer `addFailureAtNode` if possible. */ @@ -97,7 +90,7 @@ export class RuleWalker extends SyntaxWalker { /** Add a failure using a node's span. */ public addFailureAtNode(node: ts.Node, failure: string, fix?: Fix) { - this.addFailureAt(node.getStart(), node.getWidth(), failure, fix); + this.addFailureAt(node.getStart(this.sourceFile), node.getWidth(this.sourceFile), failure, fix); } public createReplacement(start: number, length: number, text: string): Replacement { @@ -112,7 +105,11 @@ export class RuleWalker extends SyntaxWalker { return this.createReplacement(start, length, ""); } - private existsFailure(failure: RuleFailure) { - return this.failures.some((f) => f.equals(failure)); + public getRuleName(): string { + return this.ruleName; + } + + public createFix(replacements: Replacement[]): Fix { + return new Fix(this.ruleName, replacements); } } diff --git a/src/language/walker/scopeAwareRuleWalker.ts b/src/language/walker/scopeAwareRuleWalker.ts index aa01d4af19e..d7ab2d002d3 100644 --- a/src/language/walker/scopeAwareRuleWalker.ts +++ b/src/language/walker/scopeAwareRuleWalker.ts @@ -17,19 +17,17 @@ import * as ts from "typescript"; +import {isScopeBoundary} from "../utils"; import {RuleWalker} from "./ruleWalker"; export abstract class ScopeAwareRuleWalker extends RuleWalker { - protected fileIsModule: boolean; private scopeStack: T[]; constructor(sourceFile: ts.SourceFile, options?: any) { super(sourceFile, options); - this.fileIsModule = ts.isExternalModule(sourceFile); - // initialize with global scope if file is not a module - this.scopeStack = this.fileIsModule ? [] : [this.createScope(sourceFile)]; + this.scopeStack = ts.isExternalModule(sourceFile) ? [] : [this.createScope(sourceFile)]; } public abstract createScope(node: ts.Node): T; @@ -40,7 +38,7 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { // get all scopes available at this depth public getAllScopes(): T[] { - return this.scopeStack.slice(); + return this.scopeStack; } public getCurrentDepth(): number { @@ -74,20 +72,6 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { } protected isScopeBoundary(node: ts.Node): boolean { - return node.kind === ts.SyntaxKind.FunctionDeclaration - || node.kind === ts.SyntaxKind.FunctionExpression - || node.kind === ts.SyntaxKind.PropertyAssignment - || node.kind === ts.SyntaxKind.ShorthandPropertyAssignment - || node.kind === ts.SyntaxKind.MethodDeclaration - || node.kind === ts.SyntaxKind.Constructor - || node.kind === ts.SyntaxKind.ModuleDeclaration - || node.kind === ts.SyntaxKind.ArrowFunction - || node.kind === ts.SyntaxKind.ParenthesizedExpression - || node.kind === ts.SyntaxKind.ClassDeclaration - || node.kind === ts.SyntaxKind.ClassExpression - || node.kind === ts.SyntaxKind.InterfaceDeclaration - || node.kind === ts.SyntaxKind.GetAccessor - || node.kind === ts.SyntaxKind.SetAccessor - || (node.kind === ts.SyntaxKind.SourceFile && this.fileIsModule); + return isScopeBoundary(node); } } diff --git a/src/language/walker/walker.ts b/src/language/walker/walker.ts new file mode 100644 index 00000000000..3c07585e418 --- /dev/null +++ b/src/language/walker/walker.ts @@ -0,0 +1,26 @@ +/** + * @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 * as ts from "typescript"; + +import {RuleFailure} from "../rule/rule"; + +export interface IWalker { + getSourceFile(): ts.SourceFile; + walk(node: ts.Node): void; + getFailures(): RuleFailure[]; +}