From 69c4674db2a3a5650efc81511e5148ddfe926855 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Wed, 23 Nov 2016 12:21:08 -0500 Subject: [PATCH 01/48] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf7707f374..849622ab17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ Change Log v4.0.2 --- * [enhancement] Don't exit when a rule can't be found. Print as a warning instead (#1771) +* [api-change] Allow 3rd party apps to see exception when the config is invalid (#1764) * [bugfix] Don't flag a property named as empty string as not needing quotes in an object literal (#1762) * [bugfix] Report correct number of fixes done by --fix (#1767) -* [bugfix] Allow 3rd party apps to see exception when the config is invalid (#1764) * [bugfix] Fix false positives and exceptions in `prefer-for-of` (#1758) * [bugfix] Fix `adjacent-overload-signatures` false positive when a static function has the same name (#1772) From e0869e4308ee35d293b5fd21c6a88dbff12c25a1 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 23 Nov 2016 13:18:36 -0800 Subject: [PATCH 02/48] Make only-arrow-functions allow functions with a `this` parameter (#1597) --- src/rules/onlyArrowFunctionsRule.ts | 23 +++++++++++++++---- .../allow-declarations/test.ts.lint | 3 +++ .../only-arrow-functions/default/test.ts.lint | 3 +++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/rules/onlyArrowFunctionsRule.ts b/src/rules/onlyArrowFunctionsRule.ts index 5d144558884..ad7734e071b 100644 --- a/src/rules/onlyArrowFunctionsRule.ts +++ b/src/rules/onlyArrowFunctionsRule.ts @@ -56,16 +56,31 @@ export class Rule extends Lint.Rules.AbstractRule { class OnlyArrowFunctionsWalker extends Lint.RuleWalker { public visitFunctionDeclaration(node: ts.FunctionDeclaration) { - if (!node.asteriskToken && !this.hasOption(OPTION_ALLOW_DECLARATIONS)) { - this.addFailure(this.createFailure(node.getStart(), "function".length, Rule.FAILURE_STRING)); + if (!this.hasOption(OPTION_ALLOW_DECLARATIONS)) { + this.failUnlessExempt(node); } super.visitFunctionDeclaration(node); } public visitFunctionExpression(node: ts.FunctionExpression) { - if (!node.asteriskToken) { + this.failUnlessExempt(node); + super.visitFunctionExpression(node); + } + + private failUnlessExempt(node: ts.FunctionLikeDeclaration) { + if (!functionIsExempt(node)) { this.addFailure(this.createFailure(node.getStart(), "function".length, Rule.FAILURE_STRING)); } - super.visitFunctionExpression(node); } } + +/** Generator functions and functions explicitly declaring `this` are allowed. */ +function functionIsExempt(node: ts.FunctionLikeDeclaration) { + return node.asteriskToken || hasThisParameter(node); +} + +function hasThisParameter(node: ts.FunctionLikeDeclaration) { + const first = node.parameters[0]; + return first && first.name.kind === ts.SyntaxKind.Identifier && + (first.name as ts.Identifier).originalKeywordKind === ts.SyntaxKind.ThisKeyword; +} diff --git a/test/rules/only-arrow-functions/allow-declarations/test.ts.lint b/test/rules/only-arrow-functions/allow-declarations/test.ts.lint index 22dbf05bec4..6b0ce7a08a8 100644 --- a/test/rules/only-arrow-functions/allow-declarations/test.ts.lint +++ b/test/rules/only-arrow-functions/allow-declarations/test.ts.lint @@ -20,4 +20,7 @@ function () { function* generator() {} let generator = function*() {} +function hasThisParameter(this) {} +let hasThisParameter = function(this) {} + [0]: non-arrow functions are forbidden diff --git a/test/rules/only-arrow-functions/default/test.ts.lint b/test/rules/only-arrow-functions/default/test.ts.lint index ddacd8e2fc5..bad11677983 100644 --- a/test/rules/only-arrow-functions/default/test.ts.lint +++ b/test/rules/only-arrow-functions/default/test.ts.lint @@ -23,4 +23,7 @@ function () { function* generator() {} let generator = function*() {} +function hasThisParameter(this) {} +let hasThisParameter = function(this) {} + [0]: non-arrow functions are forbidden From 41428a6800538760ebcb194675d8c045e0eea585 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 23 Nov 2016 14:49:04 -0800 Subject: [PATCH 03/48] Check for newly introduced diagnostics before reporting failures (for unused import) (#1608) --- src/language/languageServiceHost.ts | 51 +++++++++++++++++++ src/language/rule/abstractRule.ts | 2 +- src/language/rule/rule.ts | 2 +- src/language/rule/typedRule.ts | 4 +- src/linter.ts | 11 +++- src/rules/completedDocsRule.ts | 4 +- src/rules/noForInArrayRule.ts | 4 +- src/rules/noMergeableNamespaceRule.ts | 3 +- src/rules/noUnusedVariableRule.ts | 35 +++++++++++-- src/rules/noUseBeforeDeclareRule.ts | 3 +- src/rules/preferForOfRule.ts | 1 + src/rules/restrictPlusOperandsRule.ts | 4 +- src/test.ts | 20 +++++++- .../no-unused-variable/type-checked/a.ts | 2 + .../type-checked/test.ts.lint | 5 ++ .../type-checked/tsconfig.json | 5 ++ .../type-checked/tslint.json | 8 +++ 17 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 test/rules/no-unused-variable/type-checked/a.ts create mode 100644 test/rules/no-unused-variable/type-checked/test.ts.lint create mode 100644 test/rules/no-unused-variable/type-checked/tsconfig.json create mode 100644 test/rules/no-unused-variable/type-checked/tslint.json diff --git a/src/language/languageServiceHost.ts b/src/language/languageServiceHost.ts index 180e110c054..fd00e8fd5c2 100644 --- a/src/language/languageServiceHost.ts +++ b/src/language/languageServiceHost.ts @@ -19,6 +19,57 @@ import * as ts from "typescript"; import {createCompilerOptions} from "./utils"; +interface LanguageServiceEditableHost extends ts.LanguageServiceHost { + editFile(fileName: string, newContent: string): void; +} + +export function wrapProgram(program: ts.Program): ts.LanguageService { + const files: {[name: string]: string} = {}; + const fileVersions: {[name: string]: number} = {}; + const host: LanguageServiceEditableHost = { + getCompilationSettings: () => program.getCompilerOptions(), + getCurrentDirectory: () => program.getCurrentDirectory(), + getDefaultLibFileName: () => null, + getScriptFileNames: () => program.getSourceFiles().map((sf) => sf.fileName), + getScriptSnapshot: (name: string) => { + if (files.hasOwnProperty(name)) { + return ts.ScriptSnapshot.fromString(files[name]); + } + if (!program.getSourceFile(name)) { + return null; + } + return ts.ScriptSnapshot.fromString(program.getSourceFile(name).getFullText()); + }, + getScriptVersion: (name: string) => fileVersions.hasOwnProperty(name) ? fileVersions[name] + "" : "1", + log: () => { /* */ }, + editFile(fileName: string, newContent: string) { + files[fileName] = newContent; + if (fileVersions.hasOwnProperty(fileName)) { + fileVersions[fileName]++; + } else { + fileVersions[fileName] = 0; + } + }, + }; + const langSvc = ts.createLanguageService(host, ts.createDocumentRegistry()); + (langSvc as any).editFile = host.editFile; + return langSvc; +} + +export function checkEdit(ls: ts.LanguageService, sf: ts.SourceFile, newText: string) { + if (ls.hasOwnProperty("editFile")) { + const host = ls as any as LanguageServiceEditableHost; + host.editFile(sf.fileName, newText); + const newProgram = ls.getProgram(); + const newSf = newProgram.getSourceFile(sf.fileName); + const newDiags = ts.getPreEmitDiagnostics(newProgram, newSf); + // revert + host.editFile(sf.fileName, sf.getFullText()); + return newDiags; + } + return []; +} + export function createLanguageServiceHost(fileName: string, source: string): ts.LanguageServiceHost { return { getCompilationSettings: () => createCompilerOptions(), diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 1859227dd78..94a0ed38125 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -42,7 +42,7 @@ export abstract class AbstractRule implements IRule { return this.options; } - public abstract apply(sourceFile: ts.SourceFile): RuleFailure[]; + public abstract apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; public applyWithWalker(walker: RuleWalker): RuleFailure[] { walker.walk(walker.getSourceFile()); diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 3cdd626cc27..c44cb3f96e1 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -95,7 +95,7 @@ export interface IDisabledInterval { export interface IRule { getOptions(): IOptions; isEnabled(): boolean; - apply(sourceFile: ts.SourceFile): RuleFailure[]; + apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; applyWithWalker(walker: RuleWalker): RuleFailure[]; } diff --git a/src/language/rule/typedRule.ts b/src/language/rule/typedRule.ts index 661eda826c0..2e91c092bca 100644 --- a/src/language/rule/typedRule.ts +++ b/src/language/rule/typedRule.ts @@ -26,10 +26,10 @@ export abstract class TypedRule extends AbstractRule { return "applyWithProgram" in rule; } - public apply(_sourceFile: ts.SourceFile): RuleFailure[] { + public apply(): RuleFailure[] { // if no program is given to the linter, throw an error throw new Error(`${this.getOptions().ruleName} requires type checking`); } - public abstract applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]; + public abstract applyWithProgram(sourceFile: ts.SourceFile, languageService: ts.LanguageService): RuleFailure[]; } diff --git a/src/linter.ts b/src/linter.ts index e522288f358..6655415bdd7 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -31,6 +31,7 @@ import { EnableDisableRulesWalker } from "./enableDisableRules"; import { findFormatter } from "./formatterLoader"; import { ILinterOptions, LintResult } from "./index"; import { IFormatter } from "./language/formatter/formatter"; +import { createLanguageService, wrapProgram } from "./language/languageServiceHost"; import { Fix, IRule, RuleFailure } from "./language/rule/rule"; import { TypedRule } from "./language/rule/typedRule"; import * as utils from "./language/utils"; @@ -50,6 +51,7 @@ class Linter { private failures: RuleFailure[] = []; private fixes: RuleFailure[] = []; + private languageService: ts.LanguageService; /** * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. @@ -93,6 +95,10 @@ class Linter { throw new Error("ILinterOptions does not contain the property `configuration` as of version 4. " + "Did you mean to pass the `IConfigurationFile` object to lint() ? "); } + + if (program) { + this.languageService = wrapProgram(program); + } } public lint(fileName: string, source?: string, configuration: IConfigurationFile = DEFAULT_CONFIG): void { @@ -158,9 +164,9 @@ class Linter { private applyRule(rule: IRule, sourceFile: ts.SourceFile) { let ruleFailures: RuleFailure[] = []; if (this.program && TypedRule.isTypedRule(rule)) { - ruleFailures = rule.applyWithProgram(sourceFile, this.program); + ruleFailures = rule.applyWithProgram(sourceFile, this.languageService); } else { - ruleFailures = rule.apply(sourceFile); + ruleFailures = rule.apply(sourceFile, this.languageService); } let fileFailures: RuleFailure[] = []; for (let ruleFailure of ruleFailures) { @@ -201,6 +207,7 @@ class Linter { } } else { sourceFile = utils.getSourceFile(fileName, source); + this.languageService = createLanguageService(fileName, source); } if (sourceFile === undefined) { diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index a43ad746ac2..13e6c905865 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -55,9 +55,9 @@ export class Rule extends Lint.Rules.TypedRule { Rule.ARGUMENT_PROPERTIES, ]; - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { const options = this.getOptions(); - const completedDocsWalker = new CompletedDocsWalker(sourceFile, options, program); + const completedDocsWalker = new CompletedDocsWalker(sourceFile, options, langSvc.getProgram()); const nodesToCheck = this.getNodesToCheck(options.ruleArguments); completedDocsWalker.setNodesToCheck(nodesToCheck); diff --git a/src/rules/noForInArrayRule.ts b/src/rules/noForInArrayRule.ts index d8e7f949e1c..2c335a58d9f 100644 --- a/src/rules/noForInArrayRule.ts +++ b/src/rules/noForInArrayRule.ts @@ -49,8 +49,8 @@ export class Rule extends Lint.Rules.TypedRule { public static FAILURE_STRING = "for-in loops over arrays are forbidden. Use for-of or array.forEach instead."; - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - const noForInArrayWalker = new NoForInArrayWalker(sourceFile, this.getOptions(), program); + public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { + const noForInArrayWalker = new NoForInArrayWalker(sourceFile, this.getOptions(), langSvc.getProgram()); return this.applyWithWalker(noForInArrayWalker); } } diff --git a/src/rules/noMergeableNamespaceRule.ts b/src/rules/noMergeableNamespaceRule.ts index 0829b8157c2..680dc278f31 100644 --- a/src/rules/noMergeableNamespaceRule.ts +++ b/src/rules/noMergeableNamespaceRule.ts @@ -36,8 +36,7 @@ export class Rule extends Lint.Rules.AbstractRule { return `Mergeable namespace ${identifier} found. Merge its contents with the namespace on line ${locationToMerge.line}.`; } - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); + public apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] { const noMergeableNamespaceWalker = new NoMergeableNamespaceWalker(sourceFile, this.getOptions(), languageService); return this.applyWithWalker(noMergeableNamespaceWalker); } diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index 70c1703d0f3..f943c7c9cac 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -78,8 +78,7 @@ export class Rule extends Lint.Rules.AbstractRule { return `Unused ${type}: '${name}'`; } - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); + public apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] { return this.applyWithWalker(new NoUnusedVariablesWalker(sourceFile, this.getOptions(), languageService)); } } @@ -93,8 +92,10 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { private ignorePattern: RegExp; private isReactUsed: boolean; private reactImport: ts.NamespaceImport; + private possibleFailures: Lint.RuleFailure[] = []; - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, + private languageService: ts.LanguageService) { super(sourceFile, options); this.skipVariableDeclaration = false; this.skipParameterDeclaration = false; @@ -138,9 +139,33 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { if (!this.isIgnored(nameText)) { const start = this.reactImport.name.getStart(); const msg = Rule.FAILURE_STRING_FACTORY(Rule.FAILURE_TYPE_IMPORT, nameText); - this.addFailure(this.createFailure(start, nameText.length, msg)); + this.possibleFailures.push(this.createFailure(start, nameText.length, msg)); } } + + let someFixBrokeIt = false; + // Performance optimization: type-check the whole file before verifying individual fixes + if (this.possibleFailures.some((f) => f.hasFix())) { + let newText = Lint.Fix.applyAll(this.getSourceFile().getFullText(), + this.possibleFailures.map((f) => f.getFix()).filter((f) => !!f)); + + // If we have the program, we can verify that the fix doesn't introduce failures + if (Lint.checkEdit(this.languageService, this.getSourceFile(), newText).length > 0) { + console.error(`Fixes caused errors in ${this.getSourceFile().fileName}`); + someFixBrokeIt = true; + } + } + + this.possibleFailures.forEach((f) => { + if (!someFixBrokeIt || !f.hasFix()) { + this.addFailure(f); + } else { + let newText = f.getFix().apply(this.getSourceFile().getFullText()); + if (Lint.checkEdit(this.languageService, this.getSourceFile(), newText).length === 0) { + this.addFailure(f); + } + } + }); } public visitBindingElement(node: ts.BindingElement) { @@ -421,7 +446,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { if (replacements && replacements.length) { fix = new Lint.Fix(Rule.metadata.ruleName, replacements); } - this.addFailure(this.createFailure(position, name.length, Rule.FAILURE_STRING_FACTORY(type, name), fix)); + this.possibleFailures.push(this.createFailure(position, name.length, Rule.FAILURE_STRING_FACTORY(type, name), fix)); } private isIgnored(name: string) { diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index a173360422c..7eaa15de404 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -38,8 +38,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_PREFIX = "variable '"; public static FAILURE_STRING_POSTFIX = "' used before declaration"; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); + public apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] { return this.applyWithWalker(new NoUseBeforeDeclareWalker(sourceFile, this.getOptions(), languageService)); } } diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 7142a298ded..21649389c75 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -74,6 +74,7 @@ class PreferForOfWalker extends Lint.RuleWalker { if (indexVariableName != null) { const incrementorState = this.incrementorMap[indexVariableName]; if (incrementorState.onlyArrayAccess) { + // Find `array[i]`-like usages by building up a regex const length = incrementorState.endIncrementPos - node.getStart() + 1; const failure = this.createFailure(node.getStart(), length, Rule.FAILURE_STRING); this.addFailure(failure); diff --git a/src/rules/restrictPlusOperandsRule.ts b/src/rules/restrictPlusOperandsRule.ts index 05becd841fa..ec16c412894 100644 --- a/src/rules/restrictPlusOperandsRule.ts +++ b/src/rules/restrictPlusOperandsRule.ts @@ -38,8 +38,8 @@ export class Rule extends Lint.Rules.TypedRule { return `cannot add type ${type}`; } - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - return this.applyWithWalker(new RestrictPlusOperandsWalker(sourceFile, this.getOptions(), program)); + public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { + return this.applyWithWalker(new RestrictPlusOperandsWalker(sourceFile, this.getOptions(), langSvc.getProgram())); } } diff --git a/src/test.ts b/src/test.ts index 90c8e94cc75..84fcc53ada5 100644 --- a/src/test.ts +++ b/src/test.ts @@ -23,7 +23,6 @@ import * as path from "path"; import * as ts from "typescript"; import {Fix} from "./language/rule/rule"; -import {createCompilerOptions} from "./language/utils"; import * as Linter from "./linter"; import {LintError} from "./test/lintError"; import * as parse from "./test/parse"; @@ -48,6 +47,21 @@ export interface TestResult { export function runTest(testDirectory: string, rulesDirectory?: string | string[]): TestResult { const filesToLint = glob.sync(path.join(testDirectory, `**/*${MARKUP_FILE_EXTENSION}`)); const tslintConfig = Linter.findConfiguration(path.join(testDirectory, "tslint.json"), null).results; + const tsConfig = path.join(testDirectory, "tsconfig.json"); + let compilerOptions: ts.CompilerOptions = { allowJs: true }; + if (fs.existsSync(tsConfig)) { + const {config, error} = ts.readConfigFile(tsConfig, ts.sys.readFile); + if (error) { + throw new Error(JSON.stringify(error)); + } + + const parseConfigHost = { + fileExists: fs.existsSync, + readDirectory: ts.sys.readDirectory, + useCaseSensitiveFileNames: true, + }; + compilerOptions = ts.parseJsonConfigFileContent(config, parseConfigHost, testDirectory).options; + } const results: TestResult = { directory: testDirectory, results: {} }; for (const fileToLint of filesToLint) { @@ -59,7 +73,6 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ let program: ts.Program; if (tslintConfig.linterOptions && tslintConfig.linterOptions.typeCheck) { - const compilerOptions = createCompilerOptions(); const compilerHost: ts.CompilerHost = { fileExists: () => true, getCanonicalFileName: (filename: string) => filename, @@ -73,6 +86,9 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ return ts.createSourceFile(filenameToGet, fileText, compilerOptions.target); } else if (filenameToGet === fileCompileName) { return ts.createSourceFile(fileBasename, fileTextWithoutMarkup, compilerOptions.target, true); + } else if (fs.existsSync(path.resolve(path.dirname(fileToLint), filenameToGet))) { + const text = fs.readFileSync(path.resolve(path.dirname(fileToLint), filenameToGet), {encoding: "utf-8"}); + return ts.createSourceFile(filenameToGet, text, compilerOptions.target, true); } }, readFile: () => null, diff --git a/test/rules/no-unused-variable/type-checked/a.ts b/test/rules/no-unused-variable/type-checked/a.ts new file mode 100644 index 00000000000..abfc7078006 --- /dev/null +++ b/test/rules/no-unused-variable/type-checked/a.ts @@ -0,0 +1,2 @@ +export class A {} +export var a: A; diff --git a/test/rules/no-unused-variable/type-checked/test.ts.lint b/test/rules/no-unused-variable/type-checked/test.ts.lint new file mode 100644 index 00000000000..de64a5953ac --- /dev/null +++ b/test/rules/no-unused-variable/type-checked/test.ts.lint @@ -0,0 +1,5 @@ +import {a, A} from './a'; + +export class B { + static thing = a; +} diff --git a/test/rules/no-unused-variable/type-checked/tsconfig.json b/test/rules/no-unused-variable/type-checked/tsconfig.json new file mode 100644 index 00000000000..db953a729b9 --- /dev/null +++ b/test/rules/no-unused-variable/type-checked/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "declaration": true + } +} \ No newline at end of file diff --git a/test/rules/no-unused-variable/type-checked/tslint.json b/test/rules/no-unused-variable/type-checked/tslint.json new file mode 100644 index 00000000000..e81cc662cf1 --- /dev/null +++ b/test/rules/no-unused-variable/type-checked/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "no-unused-variable": true + } +} From 3d70e38c143d36fb7a0d944c5e83960323f15338 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 23 Nov 2016 19:33:41 -0500 Subject: [PATCH 04/48] Split CLI into tslint-cli and a Runner class (#1688) --- src/runner.ts | 255 ++++++++++++++++++++++++++++++++++++++++++++++ src/tslint-cli.ts | 171 ++++--------------------------- 2 files changed, 275 insertions(+), 151 deletions(-) create mode 100644 src/runner.ts diff --git a/src/runner.ts b/src/runner.ts new file mode 100644 index 00000000000..b0fde4bd8c0 --- /dev/null +++ b/src/runner.ts @@ -0,0 +1,255 @@ +/** + * @license + * Copyright 2013 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 fs from "fs"; +import * as glob from "glob"; +import * as path from "path"; +import * as ts from "typescript"; + +import { + CONFIG_FILENAME, + DEFAULT_CONFIG, + findConfiguration, +} from "./configuration"; +import { FatalError } from "./error"; +import * as Linter from "./linter"; +import { consoleTestResultHandler, runTest } from "./test"; +import { updateNotifierCheck } from "./updateNotifier"; + +export interface IRunnerOptions { + /** + * Path to a configuration file. + */ + config?: string; + + /** + * Exclude globs from path expansion. + */ + exclude?: string | string[]; + + /** + * File paths to lint. + */ + files?: string[]; + + /** + * Whether to return status code 0 even if there are lint errors. + */ + force?: boolean; + + /** + * Whether to fixes linting errors for select rules. This may overwrite linted files. + */ + fix?: boolean; + + /** + * Output format. + */ + format?: string; + + /** + * Formatters directory path. + */ + formattersDirectory?: string; + + /** + * Whether to generate a tslint.json config file in the current working directory. + */ + init?: boolean; + + /** + * Output file path. + */ + out?: string; + + /** + * tsconfig.json file. + */ + project?: string; + + /** + * Rules directory paths. + */ + rulesDirectory?: string | string[]; + + /** + * That TSLint produces the correct output for the specified directory. + */ + test?: string; + + /** + * Whether to enable type checking when linting a project. + */ + typeCheck?: boolean; + + /** + * Current TSLint version. + */ + version?: boolean; +} + +export class Runner { + private static trimSingleQuotes(str: string) { + return str.replace(/^'|'$/g, ""); + } + + constructor(private options: IRunnerOptions, private outputStream: NodeJS.WritableStream) { } + + public run(onComplete: (status: number) => void) { + if (this.options.version != null) { + this.outputStream.write(Linter.VERSION + "\n"); + onComplete(0); + return; + } + + if (this.options.init != null) { + if (fs.existsSync(CONFIG_FILENAME)) { + console.error(`Cannot generate ${CONFIG_FILENAME}: file already exists`); + onComplete(1); + return; + } + + const tslintJSON = JSON.stringify(DEFAULT_CONFIG, undefined, " "); + fs.writeFileSync(CONFIG_FILENAME, tslintJSON); + onComplete(0); + return; + } + + if (this.options.test != null) { + const results = runTest(this.options.test, this.options.rulesDirectory); + const didAllTestsPass = consoleTestResultHandler(results); + onComplete(didAllTestsPass ? 0 : 1); + return; + } + + // when provided, it should point to an existing location + if (this.options.config && !fs.existsSync(this.options.config)) { + console.error("Invalid option for configuration: " + this.options.config); + onComplete(1); + return; + } + + // if both files and tsconfig are present, use files + let files = this.options.files; + let program: ts.Program; + + if (this.options.project != null) { + if (!fs.existsSync(this.options.project)) { + console.error("Invalid option for project: " + this.options.project); + onComplete(1); + } + program = Linter.createProgram(this.options.project, path.dirname(this.options.project)); + if (files.length === 0) { + files = Linter.getFileNames(program); + } + if (this.options.typeCheck) { + // if type checking, run the type checker + const diagnostics = ts.getPreEmitDiagnostics(program); + if (diagnostics.length > 0) { + const messages = diagnostics.map((diag) => { + // emit any error messages + let message = ts.DiagnosticCategory[diag.category]; + if (diag.file) { + const {line, character} = diag.file.getLineAndCharacterOfPosition(diag.start); + message += ` at ${diag.file.fileName}:${line + 1}:${character + 1}:`; + } + message += " " + ts.flattenDiagnosticMessageText(diag.messageText, "\n"); + return message; + }); + throw new Error(messages.join("\n")); + } + } else { + // if not type checking, we don't need to pass in a program object + program = undefined; + } + } + + let ignorePatterns: string[] = []; + if (this.options.exclude) { + const excludeArguments: string[] = Array.isArray(this.options.exclude) ? this.options.exclude : [this.options.exclude]; + + ignorePatterns = excludeArguments.map(Runner.trimSingleQuotes); + } + + files = files + // remove single quotes which break matching on Windows when glob is passed in single quotes + .map(Runner.trimSingleQuotes) + .map((file: string) => glob.sync(file, { ignore: ignorePatterns, nodir: true })) + .reduce((a: string[], b: string[]) => a.concat(b)); + + try { + this.processFiles(onComplete, files, program); + } catch (error) { + if (error.name === FatalError.NAME) { + console.error(error.message); + onComplete(1); + } + // rethrow unhandled error + throw error; + } + } + + private processFiles(onComplete: (status: number) => void, files: string[], program?: ts.Program) { + const possibleConfigAbsolutePath = this.options.config != null ? path.resolve(this.options.config) : null; + const linter = new Linter({ + fix: this.options.fix, + formatter: this.options.format, + formattersDirectory: this.options.formattersDirectory || "", + rulesDirectory: this.options.rulesDirectory || "", + }, program); + + for (const file of files) { + if (!fs.existsSync(file)) { + console.error(`Unable to open file: ${file}`); + onComplete(1); + } + + const buffer = new Buffer(256); + buffer.fill(0); + const fd = fs.openSync(file, "r"); + try { + fs.readSync(fd, buffer, 0, 256, null); + if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) { + // MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame + // separator, repeating every 188 bytes. It is unlikely to find that pattern in + // TypeScript source, so tslint ignores files with the specific pattern. + console.warn(`${file}: ignoring MPEG transport stream`); + return; + } + } finally { + fs.closeSync(fd); + } + + const contents = fs.readFileSync(file, "utf8"); + const configLoad = findConfiguration(possibleConfigAbsolutePath, file); + linter.lint(file, contents, configLoad.results); + } + + const lintResult = linter.getResult(); + + this.outputStream.write(lintResult.output, () => { + if (lintResult.failureCount > 0) { + onComplete(this.options.force ? 0 : 2); + } + }); + + if (lintResult.format === "prose") { + // Check to see if there are any updates available + updateNotifierCheck(); + } + } +} diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index c8f2dc3e9a6..d7910494930 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -16,22 +16,11 @@ */ import * as fs from "fs"; -import * as glob from "glob"; import * as optimist from "optimist"; -import * as path from "path"; -import * as ts from "typescript"; -import { - CONFIG_FILENAME, - DEFAULT_CONFIG, - findConfiguration, -} from "./configuration"; -import { FatalError } from "./error"; -import * as Linter from "./linter"; -import { consoleTestResultHandler, runTest } from "./test"; -import { updateNotifierCheck } from "./updateNotifier"; +import { IRunnerOptions, Runner } from "./runner"; -let processed = optimist +const processed = optimist .usage("Usage: $0 [options] file ...") .check((argv: any) => { // at least one of file, help, version, project or unqualified argument must be present @@ -102,7 +91,7 @@ let processed = optimist }); const argv = processed.argv; -let outputStream: any; +let outputStream: NodeJS.WritableStream; if (argv.o != null) { outputStream = fs.createWriteStream(argv.o, { flags: "w+", @@ -112,28 +101,6 @@ if (argv.o != null) { outputStream = process.stdout; } -if (argv.v != null) { - outputStream.write(Linter.VERSION + "\n"); - process.exit(0); -} - -if (argv.i != null) { - if (fs.existsSync(CONFIG_FILENAME)) { - console.error(`Cannot generate ${CONFIG_FILENAME}: file already exists`); - process.exit(1); - } - - const tslintJSON = JSON.stringify(DEFAULT_CONFIG, undefined, " "); - fs.writeFileSync(CONFIG_FILENAME, tslintJSON); - process.exit(0); -} - -if (argv.test != null) { - const results = runTest(argv.test, argv.r); - const didAllTestsPass = consoleTestResultHandler(results); - process.exit(didAllTestsPass ? 0 : 1); -} - if ("help" in argv) { outputStream.write(processed.help()); const outputString = ` @@ -220,120 +187,22 @@ tslint accepts the following commandline options: process.exit(0); } -// when provided, it should point to an existing location -if (argv.c && !fs.existsSync(argv.c)) { - console.error("Invalid option for configuration: " + argv.c); - process.exit(1); -} -const possibleConfigAbsolutePath = argv.c != null ? path.resolve(argv.c) : null; - -const processFiles = (files: string[], program?: ts.Program) => { - - const linter = new Linter({ - fix: argv.fix, - formatter: argv.t, - formattersDirectory: argv.s || "", - rulesDirectory: argv.r || "", - }, program); - - for (const file of files) { - if (!fs.existsSync(file)) { - console.error(`Unable to open file: ${file}`); - process.exit(1); - } - - const buffer = new Buffer(256); - buffer.fill(0); - const fd = fs.openSync(file, "r"); - try { - fs.readSync(fd, buffer, 0, 256, null); - if (buffer.readInt8(0) === 0x47 && buffer.readInt8(188) === 0x47) { - // MPEG transport streams use the '.ts' file extension. They use 0x47 as the frame - // separator, repeating every 188 bytes. It is unlikely to find that pattern in - // TypeScript source, so tslint ignores files with the specific pattern. - console.warn(`${file}: ignoring MPEG transport stream`); - return; - } - } finally { - fs.closeSync(fd); - } - - const contents = fs.readFileSync(file, "utf8"); - const configLoad = findConfiguration(possibleConfigAbsolutePath, file); - linter.lint(file, contents, configLoad.results); - } - - const lintResult = linter.getResult(); - - outputStream.write(lintResult.output, () => { - if (lintResult.failureCount > 0) { - process.exit(argv.force ? 0 : 2); - } - }); - - if (lintResult.format === "prose") { - // Check to see if there are any updates available - updateNotifierCheck(); - } +const options: IRunnerOptions = { + config: argv.c, + exclude: argv.exclude, + files: argv._, + fix: argv.fix, + force: argv.force, + format: argv.t, + formattersDirectory: argv.s, + init: argv.init, + out: argv.out, + project: argv.project, + rulesDirectory: argv.r, + test: argv.test, + typeCheck: argv["type-check"], + version: argv.v, }; -// if both files and tsconfig are present, use files -let files = argv._; -let program: ts.Program; - -if (argv.project != null) { - if (!fs.existsSync(argv.project)) { - console.error("Invalid option for project: " + argv.project); - process.exit(1); - } - program = Linter.createProgram(argv.project, path.dirname(argv.project)); - if (files.length === 0) { - files = Linter.getFileNames(program); - } - if (argv["type-check"]) { - // if type checking, run the type checker - const diagnostics = ts.getPreEmitDiagnostics(program); - if (diagnostics.length > 0) { - const messages = diagnostics.map((diag) => { - // emit any error messages - let message = ts.DiagnosticCategory[diag.category]; - if (diag.file) { - const {line, character} = diag.file.getLineAndCharacterOfPosition(diag.start); - message += ` at ${diag.file.fileName}:${line + 1}:${character + 1}:`; - } - message += " " + ts.flattenDiagnosticMessageText(diag.messageText, "\n"); - return message; - }); - throw new Error(messages.join("\n")); - } - } else { - // if not type checking, we don't need to pass in a program object - program = undefined; - } -} - -const trimSingleQuotes = (str: string) => str.replace(/^'|'$/g, ""); - -let ignorePatterns: string[] = []; -if (argv.e) { - const excludeArguments: string[] = Array.isArray(argv.e) ? argv.e : [argv.e]; - - ignorePatterns = excludeArguments.map(trimSingleQuotes); -} - -files = files - // remove single quotes which break matching on Windows when glob is passed in single quotes - .map(trimSingleQuotes) - .map((file: string) => glob.sync(file, { ignore: ignorePatterns, nodir: true })) - .reduce((a: string[], b: string[]) => a.concat(b)); - -try { - processFiles(files, program); -} catch (error) { - if (error.name === FatalError.NAME) { - console.error(error.message); - process.exit(1); - } - // rethrow unhandled error - throw error; -} +new Runner(options, outputStream) + .run((status: number) => process.exit(status)); From 79dbc86b22c150014c4485dffd5ddace23885735 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Wed, 23 Nov 2016 20:54:45 -0500 Subject: [PATCH 05/48] remove errant comment from merge conflict --- src/rules/preferForOfRule.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 21649389c75..7142a298ded 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -74,7 +74,6 @@ class PreferForOfWalker extends Lint.RuleWalker { if (indexVariableName != null) { const incrementorState = this.incrementorMap[indexVariableName]; if (incrementorState.onlyArrayAccess) { - // Find `array[i]`-like usages by building up a regex const length = incrementorState.endIncrementPos - node.getStart() + 1; const failure = this.createFailure(node.getStart(), length, Rule.FAILURE_STRING); this.addFailure(failure); From fa8f6443666c1a62d8573119d5d8d72f5c21eacb Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 23 Nov 2016 20:59:55 -0500 Subject: [PATCH 06/48] Narrowed no-internal-module failure to the keyword (#1781) Fixes #939. --- src/rules/noInternalModuleRule.ts | 9 ++++-- test/rules/no-internal-module/test.ts.lint | 33 ++++++---------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/rules/noInternalModuleRule.ts b/src/rules/noInternalModuleRule.ts index 751a4e8a75f..f22c0d5ef9a 100644 --- a/src/rules/noInternalModuleRule.ts +++ b/src/rules/noInternalModuleRule.ts @@ -43,7 +43,8 @@ export class Rule extends Lint.Rules.AbstractRule { class NoInternalModuleWalker extends Lint.RuleWalker { public visitModuleDeclaration(node: ts.ModuleDeclaration) { if (this.isInternalModuleDeclaration(node)) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + const start = this.getStartBeforeModule(node); + this.addFailure(this.createFailure(node.getStart() + start, "module".length, Rule.FAILURE_STRING)); } super.visitModuleDeclaration(node); } @@ -56,10 +57,14 @@ class NoInternalModuleWalker extends Lint.RuleWalker { && node.name.kind === ts.SyntaxKind.Identifier && !isGlobalAugmentation(node); } + + private getStartBeforeModule(node: ts.ModuleDeclaration) { + return node.getText().indexOf("module"); + } } function isGlobalAugmentation(node: ts.ModuleDeclaration) { - // augmenting global uses a sepcial syntax that is allowed + // augmenting global uses a special syntax that is allowed // see https://github.com/Microsoft/TypeScript/pull/6213 return node.name.kind === ts.SyntaxKind.Identifier && node.name.text === "global"; } diff --git a/test/rules/no-internal-module/test.ts.lint b/test/rules/no-internal-module/test.ts.lint index 3bae9ee7fcd..e6f0fed49b0 100644 --- a/test/rules/no-internal-module/test.ts.lint +++ b/test/rules/no-internal-module/test.ts.lint @@ -5,14 +5,12 @@ namespace foo { declare global { } module bar { -~~~~~~~~~~~~ +~~~~~~ [0] } -~ [0] declare module buzz { -~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ [0] } -~ [0] declare module "hoge" { } @@ -30,29 +28,20 @@ namespace foo { namespace foo.bar { module baz { - ~~~~~~~~~~~~ + ~~~~~~ [0] namespace buzz { -~~~~~~~~~~~~~~~~~~~~~~~~ } -~~~~~~~~~ } -~~~~~ [0] } module foo.bar { -~~~~~~~~~~~~~~~~ +~~~~~~ [0] namespace baz { -~~~~~~~~~~~~~~~~~~~ module buzz { -~~~~~~~~~~~~~~~~~~~~~ - ~~~~~~~~~~~~~ + ~~~~~~ [0] } -~~~~~~~~~ -~~~~~~~~~ [0] } -~~~~~ } -~ [0] namespace name.namespace { } @@ -61,21 +50,17 @@ namespace namespace.name { // intentionally malformed for test cases, do not format declare module declare -~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ [0] .dec{} -~~~~~~ [0] declare module dec . declare { -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~ [0] } -~ [0] module mod.module{} -~~~~~~~~~~~~~~~~~~~~ [0] +~~~~~~ [0] module module.mod -~~~~~~~~~~~~~~~~~ +~~~~~~ [0] { -~ } -~ [0] [0]: The internal 'module' syntax is deprecated, use the 'namespace' keyword instead. From 3609b2ec341ea8388ebbd618088808663417adcf Mon Sep 17 00:00:00 2001 From: Andrii Dieiev Date: Thu, 24 Nov 2016 04:46:16 +0200 Subject: [PATCH 07/48] Workaround for #1400. Excludes imported properties from error (#1620) --- src/rules/noUseBeforeDeclareRule.ts | 15 ++++++++++++++- test/rules/no-use-before-declare/test.ts.lint | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index 7eaa15de404..c626729f72c 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -46,6 +46,8 @@ export class Rule extends Lint.Rules.AbstractRule { type VisitedVariables = {[varName: string]: boolean}; class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { + private importedPropertiesPositions: number[] = []; + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { super(sourceFile, options); } @@ -89,6 +91,9 @@ class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { dog++; }; b({ c: 5 }); } + +import { ITest as ITest0 } from './ImportRegularAlias'; +import {/*ensure comments works*/ ITest as ITest1 } from './ImportAliasWithComment'; +import { + ITest as ITest2 +} from './ImportWithLineBreaks'; +import {First, ITest as ITest3 } from './ImportAliasSecond'; + +import ITest from './InterfaceFile'; From d29fecb6f9041f52ad317c718d857acc04250ad0 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Thu, 24 Nov 2016 06:28:00 +0100 Subject: [PATCH 08/48] Allow undefined parameter type for hasModifier (#1749) --- src/language/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/language/utils.ts b/src/language/utils.ts index 2a33a7a6fe9..707f6b52b5e 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -77,8 +77,8 @@ export function scanAllTokens(scanner: ts.Scanner, callback: (s: ts.Scanner) => /** * @returns true if any modifier kinds passed along exist in the given modifiers array */ -export function hasModifier(modifiers: ts.ModifiersArray, ...modifierKinds: ts.SyntaxKind[]) { - if (modifiers == null || modifierKinds == null) { +export function hasModifier(modifiers: ts.ModifiersArray|undefined, ...modifierKinds: ts.SyntaxKind[]) { + if (modifiers === undefined || modifierKinds.length === 0) { return false; } From 8d920c85180dae7addec7a79b376fb29103349f8 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 24 Nov 2016 01:02:40 -0500 Subject: [PATCH 09/48] Make CLI arg types stronger (#1783) CLI arguments that were actually boolean flags but weren't marked as such would swallow the next argument, even if it was another flag. For example tslint --type-check --project x would set type-check="--project" Marking these as boolean with optimist also ensures that the value is either true or false... and not undefined --- src/runner.ts | 16 ++++++++-------- src/tslint-cli.ts | 13 ++++++++++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/runner.ts b/src/runner.ts index b0fde4bd8c0..3c82a5f026f 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -49,12 +49,12 @@ export interface IRunnerOptions { /** * Whether to return status code 0 even if there are lint errors. */ - force?: boolean; + force: boolean; /** * Whether to fixes linting errors for select rules. This may overwrite linted files. */ - fix?: boolean; + fix: boolean; /** * Output format. @@ -69,7 +69,7 @@ export interface IRunnerOptions { /** * Whether to generate a tslint.json config file in the current working directory. */ - init?: boolean; + init: boolean; /** * Output file path. @@ -94,12 +94,12 @@ export interface IRunnerOptions { /** * Whether to enable type checking when linting a project. */ - typeCheck?: boolean; + typeCheck: boolean; /** * Current TSLint version. */ - version?: boolean; + version: boolean; } export class Runner { @@ -110,13 +110,13 @@ export class Runner { constructor(private options: IRunnerOptions, private outputStream: NodeJS.WritableStream) { } public run(onComplete: (status: number) => void) { - if (this.options.version != null) { + if (this.options.version) { this.outputStream.write(Linter.VERSION + "\n"); onComplete(0); return; } - if (this.options.init != null) { + if (this.options.init) { if (fs.existsSync(CONFIG_FILENAME)) { console.error(`Cannot generate ${CONFIG_FILENAME}: file already exists`); onComplete(1); @@ -129,7 +129,7 @@ export class Runner { return; } - if (this.options.test != null) { + if (this.options.test) { const results = runTest(this.options.test, this.options.rulesDirectory); const didAllTestsPass = consoleTestResultHandler(results); onComplete(didAllTestsPass ? 0 : 1); diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index d7910494930..c91b8f1b81b 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -36,6 +36,7 @@ const processed = optimist "c": { alias: "config", describe: "configuration file", + type: "string", }, "e": { alias: "exclude", @@ -53,40 +54,50 @@ const processed = optimist "h": { alias: "help", describe: "display detailed help", + type: "boolean", }, "i": { alias: "init", describe: "generate a tslint.json config file in the current working directory", + type: "boolean", }, "o": { alias: "out", describe: "output file", + type: "string", }, "project": { describe: "tsconfig.json file", + type: "string", }, "r": { alias: "rules-dir", describe: "rules directory", + type: "string", }, "s": { alias: "formatters-dir", describe: "formatters directory", + type: "string", }, "t": { alias: "format", default: "prose", describe: "output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist)", + type: "string", }, "test": { describe: "test that tslint produces the correct output for the specified directory", + type: "string", }, "type-check": { describe: "enable type checking when linting a project", + type: "boolean", }, "v": { alias: "version", describe: "current version", + type: "boolean", }, }); const argv = processed.argv; @@ -101,7 +112,7 @@ if (argv.o != null) { outputStream = process.stdout; } -if ("help" in argv) { +if (argv.help) { outputStream.write(processed.help()); const outputString = ` tslint accepts the following commandline options: From 3def898cc5492bdf169b4ed6568d82c40558f80a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 24 Nov 2016 22:33:23 -0500 Subject: [PATCH 10/48] Loosened runner options (#1786) --- src/runner.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runner.ts b/src/runner.ts index 3c82a5f026f..7f161ba0088 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -49,12 +49,12 @@ export interface IRunnerOptions { /** * Whether to return status code 0 even if there are lint errors. */ - force: boolean; + force?: boolean; /** * Whether to fixes linting errors for select rules. This may overwrite linted files. */ - fix: boolean; + fix?: boolean; /** * Output format. @@ -69,7 +69,7 @@ export interface IRunnerOptions { /** * Whether to generate a tslint.json config file in the current working directory. */ - init: boolean; + init?: boolean; /** * Output file path. @@ -94,12 +94,12 @@ export interface IRunnerOptions { /** * Whether to enable type checking when linting a project. */ - typeCheck: boolean; + typeCheck?: boolean; /** - * Current TSLint version. + * Whether to show the current TSLint version. */ - version: boolean; + version?: boolean; } export class Runner { From 63e8a29e64ee91fccc01531a0eff614dcb622cd5 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 24 Nov 2016 22:37:15 -0500 Subject: [PATCH 11/48] Added final onComplete, returns after onCompletes (#1787) --- src/runner.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/runner.ts b/src/runner.ts index 7f161ba0088..2ab7c7571de 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -151,6 +151,7 @@ export class Runner { if (!fs.existsSync(this.options.project)) { console.error("Invalid option for project: " + this.options.project); onComplete(1); + return; } program = Linter.createProgram(this.options.project, path.dirname(this.options.project)); if (files.length === 0) { @@ -216,6 +217,7 @@ export class Runner { if (!fs.existsSync(file)) { console.error(`Unable to open file: ${file}`); onComplete(1); + return; } const buffer = new Buffer(256); @@ -244,6 +246,8 @@ export class Runner { this.outputStream.write(lintResult.output, () => { if (lintResult.failureCount > 0) { onComplete(this.options.force ? 0 : 2); + } else { + onComplete(0); } }); From 5ced33debfb19fe4137f930d9310e761c09c7ec8 Mon Sep 17 00:00:00 2001 From: Yaroslav Admin Date: Sun, 27 Nov 2016 05:49:21 +0100 Subject: [PATCH 12/48] Fixed broken list on Custom Rules page (#1791) --- docs/develop/custom-rules/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/develop/custom-rules/index.md b/docs/develop/custom-rules/index.md index 36806f85425..d2ae0fc2bb2 100644 --- a/docs/develop/custom-rules/index.md +++ b/docs/develop/custom-rules/index.md @@ -8,6 +8,7 @@ TSLint ships with a set of core rules that can be configured. However, users are Let us take the example of how to write a new rule to forbid all import statements (you know, *for science*). Let us name the rule file `noImportsRule.ts`. Rules are referenced in `tslint.json` with their kebab-cased identifer, so `"no-imports": true` would configure the rule. __Important conventions__: + - Rule identifiers are always kebab-cased. - Rule files are always camel-cased (`camelCasedRule.ts`). - Rule files *must* contain the suffix `Rule`. From f1df7b7c917fe2d3378d68aba208b69d7523f65e Mon Sep 17 00:00:00 2001 From: Andrii Dieiev Date: Sun, 27 Nov 2016 06:52:07 +0200 Subject: [PATCH 13/48] Track and restore initial or previous rule state for one line lint switches (fixes #1624) (#1634) --- src/enableDisableRules.ts | 93 ++++++++++++++----- src/language/rule/abstractRule.ts | 24 ++--- src/linter.ts | 6 +- src/ruleLoader.ts | 60 ++++-------- .../_integration/enable-disable/test.ts.lint | 16 ++++ .../_integration/enable-disable/tslint.json | 3 +- 6 files changed, 122 insertions(+), 80 deletions(-) diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index ae9f42dec9e..4c01d2ee43b 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -17,6 +17,8 @@ import * as ts from "typescript"; +import {AbstractRule} from "./language/rule/abstractRule"; +import {IOptions} from "./language/rule/rule"; import {scanAllTokens} from "./language/utils"; import {SkippableTokenAwareRuleWalker} from "./language/walker/skippableTokenAwareRuleWalker"; import {IEnableDisablePosition} from "./ruleLoader"; @@ -24,6 +26,21 @@ import {IEnableDisablePosition} from "./ruleLoader"; export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { public enableDisableRuleMap: {[rulename: string]: IEnableDisablePosition[]} = {}; + constructor(sourceFile: ts.SourceFile, options: IOptions, rules: {[name: string]: any}) { + super(sourceFile, options); + + if (rules) { + for (const rule in rules) { + if (rules.hasOwnProperty(rule) && AbstractRule.isRuleEnabled(rules[rule])) { + this.enableDisableRuleMap[rule] = [{ + isEnabled: true, + position: 0, + }]; + } + } + } + } + public visitSourceFile(node: ts.SourceFile) { super.visitSourceFile(node); const scan = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text); @@ -52,6 +69,29 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { ); } + private switchRuleState(ruleName: string, isEnabled: boolean, start: number, end?: number): void { + const ruleStateMap = this.enableDisableRuleMap[ruleName]; + + ruleStateMap.push({ + isEnabled, + position: start, + }); + + if (end) { + // switchRuleState method is only called when rule state changes therefore we can safely use opposite state + ruleStateMap.push({ + isEnabled: !isEnabled, + position: end, + }); + } + } + + private getLatestRuleState(ruleName: string): boolean { + const ruleStateMap = this.enableDisableRuleMap[ruleName]; + + return ruleStateMap[ruleStateMap.length - 1].isEnabled; + } + private handlePossibleTslintSwitch(commentText: string, startingPosition: number, node: ts.SourceFile, scanner: ts.Scanner) { // regex is: start of string followed by "/*" or "//" followed by any amount of whitespace followed by "tslint:" if (commentText.match(/^(\/\*|\/\/)\s*tslint:/)) { @@ -82,35 +122,44 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { rulesList = commentTextParts[2].split(/\s+/); } - for (const ruleToAdd of rulesList) { - if (!(ruleToAdd in this.enableDisableRuleMap)) { - this.enableDisableRuleMap[ruleToAdd] = []; + if (rulesList.indexOf("all") !== -1) { + // iterate over all enabled rules + rulesList = Object.keys(this.enableDisableRuleMap); + } + + for (const ruleToSwitch of rulesList) { + if (!(ruleToSwitch in this.enableDisableRuleMap)) { + // all rules enabled in configuration are already in map - skip switches for disabled rules + continue; + } + + const previousState = this.getLatestRuleState(ruleToSwitch); + + if (previousState === isEnabled) { + // no need to add switch points if there is no change in rule state + continue; } + + let start: number; + let end: number; + if (isCurrentLine) { // start at the beginning of the current line - this.enableDisableRuleMap[ruleToAdd].push({ - isEnabled, - position: this.getStartOfLinePosition(node, startingPosition), - }); + start = this.getStartOfLinePosition(node, startingPosition); // end at the beginning of the next line - this.enableDisableRuleMap[ruleToAdd].push({ - isEnabled: !isEnabled, - position: scanner.getTextPos() + 1, - }); - } else { + end = scanner.getTextPos() + 1; + } else if (isNextLine) { // start at the current position - this.enableDisableRuleMap[ruleToAdd].push({ - isEnabled, - position: startingPosition, - }); + start = startingPosition; // end at the beginning of the line following the next line - if (isNextLine) { - this.enableDisableRuleMap[ruleToAdd].push({ - isEnabled: !isEnabled, - position: this.getStartOfLinePosition(node, startingPosition, 2), - }); - } + end = this.getStartOfLinePosition(node, startingPosition, 2); + } else { + // disable rule for the rest of the file + // start at the current position, but skip end position + start = startingPosition; } + + this.switchRuleState(ruleToSwitch, isEnabled, start, end); } } } diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 94a0ed38125..3d7f7aed9ec 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -24,6 +24,18 @@ export abstract class AbstractRule implements IRule { public static metadata: IRuleMetadata; private options: IOptions; + public static isRuleEnabled(ruleConfigValue: any): boolean { + if (typeof ruleConfigValue === "boolean") { + return ruleConfigValue; + } + + if (Array.isArray(ruleConfigValue) && ruleConfigValue.length > 0) { + return ruleConfigValue[0]; + } + + return false; + } + constructor(ruleName: string, private value: any, disabledIntervals: IDisabledInterval[]) { let ruleArguments: any[] = []; @@ -50,16 +62,6 @@ export abstract class AbstractRule implements IRule { } public isEnabled(): boolean { - const value = this.value; - - if (typeof value === "boolean") { - return value; - } - - if (Array.isArray(value) && value.length > 0) { - return value[0]; - } - - return false; + return AbstractRule.isRuleEnabled(this.value); } } diff --git a/src/linter.ts b/src/linter.ts index 6655415bdd7..f83357f85df 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -179,19 +179,19 @@ class Linter { private getEnabledRules(fileName: string, source?: string, configuration: IConfigurationFile = DEFAULT_CONFIG): IRule[] { const sourceFile = this.getSourceFile(fileName, source); + const isJs = /\.jsx?$/i.test(fileName); + const configurationRules = isJs ? configuration.jsRules : configuration.rules; // walk the code first to find all the intervals where rules are disabled const rulesWalker = new EnableDisableRulesWalker(sourceFile, { disabledIntervals: [], ruleName: "", - }); + }, configurationRules); rulesWalker.walk(sourceFile); const enableDisableRuleMap = rulesWalker.enableDisableRuleMap; const rulesDirectories = arrayify(this.options.rulesDirectory) .concat(arrayify(configuration.rulesDirectory)); - const isJs = /\.jsx?$/i.test(fileName); - const configurationRules = isJs ? configuration.jsRules : configuration.rules; let configuredRules = loadRules(configurationRules, enableDisableRuleMap, rulesDirectories, isJs); return configuredRules.filter((r) => r.isEnabled()); diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index 7c6cebb5047..2a93f67c103 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -50,10 +50,8 @@ export function loadRules(ruleConfiguration: {[name: string]: any}, if (isJs && Rule.metadata && Rule.metadata.typescriptOnly != null && Rule.metadata.typescriptOnly) { notAllowedInJsRules.push(ruleName); } else { - const all = "all"; // make the linter happy until we can turn it on and off - const allList = (all in enableDisableRuleMap ? enableDisableRuleMap[all] : []); const ruleSpecificList = (ruleName in enableDisableRuleMap ? enableDisableRuleMap[ruleName] : []); - const disabledIntervals = buildDisabledIntervalsFromSwitches(ruleSpecificList, allList); + const disabledIntervals = buildDisabledIntervalsFromSwitches(ruleSpecificList); rules.push(new Rule(ruleName, ruleValue, disabledIntervals)); if (Rule.metadata && Rule.metadata.deprecationMessage && shownDeprecations.indexOf(Rule.metadata.ruleName) === -1) { @@ -141,52 +139,28 @@ function loadRule(directory: string, ruleName: string) { return undefined; } -/* - * We're assuming both lists are already sorted top-down so compare the tops, use the smallest of the two, - * and build the intervals that way. +/** + * creates disabled intervals for rule based on list of switchers for it + * @param ruleSpecificList - contains all switchers for rule states sorted top-down and strictly alternating between enabled and disabled */ -function buildDisabledIntervalsFromSwitches(ruleSpecificList: IEnableDisablePosition[], allList: IEnableDisablePosition[]) { - let isCurrentlyDisabled = false; - let disabledStartPosition: number; +function buildDisabledIntervalsFromSwitches(ruleSpecificList: IEnableDisablePosition[]) { const disabledIntervalList: IDisabledInterval[] = []; - let i = 0; - let j = 0; - - while (i < ruleSpecificList.length || j < allList.length) { - const ruleSpecificTopPositon = (i < ruleSpecificList.length ? ruleSpecificList[i].position : Infinity); - const allTopPositon = (j < allList.length ? allList[j].position : Infinity); - let newPositionToCheck: IEnableDisablePosition; - if (ruleSpecificTopPositon < allTopPositon) { - newPositionToCheck = ruleSpecificList[i]; - i++; - } else { - newPositionToCheck = allList[j]; - j++; - } + // starting from second element in the list since first is always enabled in position 0; + let i = 1; - // we're currently disabled and enabling, or currently enabled and disabling -- a switch - if (newPositionToCheck.isEnabled === isCurrentlyDisabled) { - if (!isCurrentlyDisabled) { - // start a new interval - disabledStartPosition = newPositionToCheck.position; - isCurrentlyDisabled = true; - } else { - // we're currently disabled and about to enable -- end the interval - disabledIntervalList.push({ - endPosition: newPositionToCheck.position, - startPosition: disabledStartPosition, - }); - isCurrentlyDisabled = false; - } - } - } + while (i < ruleSpecificList.length) { + const startPosition = ruleSpecificList[i].position; + + // rule enabled state is always alternating therefore we can use position of next switch as end of disabled interval + // set endPosition as Infinity in case when last switch for rule in a file is disabled + const endPosition = ruleSpecificList[i + 1] ? ruleSpecificList[i + 1].position : Infinity; - if (isCurrentlyDisabled) { - // we started an interval but didn't finish one -- so finish it with an Infinity disabledIntervalList.push({ - endPosition: Infinity, - startPosition: disabledStartPosition, + endPosition, + startPosition, }); + + i += 2; } return disabledIntervalList; diff --git a/test/rules/_integration/enable-disable/test.ts.lint b/test/rules/_integration/enable-disable/test.ts.lint index a628d1bc4d8..fd0dd3694df 100644 --- a/test/rules/_integration/enable-disable/test.ts.lint +++ b/test/rules/_integration/enable-disable/test.ts.lint @@ -64,3 +64,19 @@ var AAAaA = 'test' /* tslint:disable:quotemark */ var s = 'xxx'; + +//Test case for issue #1624 +// tslint:disable:quotemark variable-name +var AAAaA = 'test' // tslint:disable-line:quotemark +// tslint:disable-next-line:variable-name +var AAAaA = 'test' //previously `quotemark` rule was enabled after previous line + +var AAAaA = 'test' // previously both `quotemark` and `variable-name` rules were enabled after previous lines + +// tslint:enable:quotemark +// tslint:disable +var AAAaA = 'test' // tslint:disable-line:quotemark +var AAAaA = 'test' // ensure that disable-line rule correctly handles previous `enable rule - disable all` switches + +// tslint:enable:no-var-keyword +var AAAaA = 'test' // ensure that disabled in config rule isn't enabled diff --git a/test/rules/_integration/enable-disable/tslint.json b/test/rules/_integration/enable-disable/tslint.json index a8de85ad1d7..6153bd7842b 100644 --- a/test/rules/_integration/enable-disable/tslint.json +++ b/test/rules/_integration/enable-disable/tslint.json @@ -1,6 +1,7 @@ { "rules": { "quotemark": [true, "double"], - "variable-name": true + "variable-name": true, + "no-var-keyword": false } } From 5c7cf4a5df7c1016c5c7e6f6594960bd2547e9c2 Mon Sep 17 00:00:00 2001 From: Ryan Lester Date: Sun, 27 Nov 2016 00:19:50 -0500 Subject: [PATCH 14/48] Rule: `Promise`-returning methods must be `async` (#1779) --- src/rules/promiseFunctionAsyncRule.ts | 89 +++++++++++++++++++ .../rules/promise-function-async/test.ts.lint | 67 ++++++++++++++ test/rules/promise-function-async/tslint.json | 8 ++ 3 files changed, 164 insertions(+) create mode 100644 src/rules/promiseFunctionAsyncRule.ts create mode 100644 test/rules/promise-function-async/test.ts.lint create mode 100644 test/rules/promise-function-async/tslint.json diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts new file mode 100644 index 00000000000..a058b686a44 --- /dev/null +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2016 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 * as Lint from "../index"; + +export class Rule extends Lint.Rules.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "promise-function-async", + description: "Requires any function or method that returns a promise to be marked async.", + rationale: Lint.Utils.dedent` + Ensures that each function is only capable of 1) returning a rejected promise, or 2) + throwing an Error object. In contrast, non-\`async\` \`Promise\`-returning functions + are technically capable of either. This practice removes a requirement for consuming + code to handle both cases. + `, + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "typescript", + typescriptOnly: false, + requiresTypeInfo: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "functions that return promises must be async"; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + return this.applyWithWalker(new PromiseAsyncWalker(sourceFile, this.getOptions(), program)); + } +} + +class PromiseAsyncWalker extends Lint.ProgramAwareRuleWalker { + public visitArrowFunction(node: ts.ArrowFunction) { + this.test(node); + super.visitArrowFunction(node); + } + + public visitFunctionDeclaration(node: ts.FunctionDeclaration) { + this.test(node); + super.visitFunctionDeclaration(node); + } + + public visitFunctionExpression(node: ts.FunctionExpression) { + this.test(node); + super.visitFunctionExpression(node); + } + + public visitMethodDeclaration(node: ts.MethodDeclaration) { + this.test(node); + super.visitMethodDeclaration(node); + } + + private test(node: ts.SignatureDeclaration & { body?: ts.Node}) { + const tc = this.getTypeChecker(); + + const signature = tc.getTypeAtLocation(node).getCallSignatures()[0]; + const returnType = tc.typeToString(tc.getReturnTypeOfSignature(signature)); + + const isAsync = Lint.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword); + const isPromise = returnType.indexOf("Promise<") === 0; + + const signatureEnd = node.body ? + node.body.getStart() - node.getStart() - 1 : + node.getWidth() + ; + + if (isAsync || !isPromise) { + return; + } + + this.addFailure(this.createFailure(node.getStart(), signatureEnd, Rule.FAILURE_STRING)); + } +} diff --git a/test/rules/promise-function-async/test.ts.lint b/test/rules/promise-function-async/test.ts.lint new file mode 100644 index 00000000000..692f314831e --- /dev/null +++ b/test/rules/promise-function-async/test.ts.lint @@ -0,0 +1,67 @@ +declare class Promise{} + +const nonAsyncPromiseFunctionExpressionA = function(p: Promise) { return p; }; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +const nonAsyncPromiseFunctionExpressionB = function() { return new Promise(); }; + ~~~~~~~~~~ [0] + +// 'async' 'Promise'-returning function expressions are allowed +const asyncPromiseFunctionExpressionA = async function(p: Promise) { return p; }; +const asyncPromiseFunctionExpressionB = async function() { return new Promise(); }; + +// non-'async' non-'Promise'-returning function expressions are allowed +const nonAsyncNonPromiseFunctionExpression = function(n: number) { return n; }; + +function nonAsyncPromiseFunctionDeclarationA(p: Promise) { return p; } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +function nonAsyncPromiseFunctionDeclarationB() { return new Promise(); } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +// 'async' 'Promise'-returning function declarations are allowed +async function asyncPromiseFunctionDeclarationA(p: Promise) { return p; } +async function asyncPromiseFunctionDeclarationB() { return new Promise(); } + +// non-'async' non-'Promise'-returning function declarations are allowed +function nonAsyncNonPromiseFunctionDeclaration(n: number) { return n; } + +const nonAsyncPromiseArrowFunctionA = (p: Promise) => p; + ~~~~~~~~~~~~~~~~~~~~~ [0] + +const nonAsyncPromiseArrowFunctionB = () => new Promise(); + ~~~~~ [0] + +// 'async' 'Promise'-returning arrow functions are allowed +const asyncPromiseArrowFunctionA = async (p: Promise) => p; +const asyncPromiseArrowFunctionB = async () => new Promise(); + +// non-'async' non-'Promise'-returning arrow functions are allowed +const nonAsyncNonPromiseArrowFunction = (n: number) => n; + +class Test { + public nonAsyncPromiseMethodA(p: Promise) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + return p; + } + + public nonAsyncPromiseMethodB() { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + return new Promise(); + } + + // 'async' 'Promise'-returning methods are allowed + public async asyncPromiseMethodA(p: Promise) { + return p; + } + public async asyncPromiseMethodB() { + return new Promise(); + } + + // non-'async' non-'Promise'-returning methods are allowed + public nonAsyncNonPromiseMethod(n: number) { + return n; + } +} + +[0]: functions that return promises must be async diff --git a/test/rules/promise-function-async/tslint.json b/test/rules/promise-function-async/tslint.json new file mode 100644 index 00000000000..a5c4617c770 --- /dev/null +++ b/test/rules/promise-function-async/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "promise-function-async": true + } +} From d46f74f7a7ac084aa4e06baa036e25e04b13b2aa Mon Sep 17 00:00:00 2001 From: Yaroslav Admin Date: Sun, 27 Nov 2016 16:49:42 +0100 Subject: [PATCH 15/48] Implemented fix for quotemark rule (#1790) --- src/rules/quotemarkRule.ts | 7 ++++++- test/rules/quotemark/double-avoid-escape/test.ts.fix | 4 ++++ test/rules/quotemark/double/test.ts.fix | 4 ++++ test/rules/quotemark/jsx-double/test.tsx.fix | 4 ++++ test/rules/quotemark/jsx-single/test.tsx.fix | 4 ++++ test/rules/quotemark/single-avoid-escape/test.ts.fix | 4 ++++ test/rules/quotemark/single/test.ts.fix | 4 ++++ 7 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 test/rules/quotemark/double-avoid-escape/test.ts.fix create mode 100644 test/rules/quotemark/double/test.ts.fix create mode 100644 test/rules/quotemark/jsx-double/test.tsx.fix create mode 100644 test/rules/quotemark/jsx-single/test.tsx.fix create mode 100644 test/rules/quotemark/single-avoid-escape/test.ts.fix create mode 100644 test/rules/quotemark/single/test.ts.fix diff --git a/src/rules/quotemarkRule.ts b/src/rules/quotemarkRule.ts index ebfb7bda6f3..cc99af3a18a 100644 --- a/src/rules/quotemarkRule.ts +++ b/src/rules/quotemarkRule.ts @@ -116,7 +116,12 @@ class QuotemarkWalker extends Lint.RuleWalker { ? Rule.SINGLE_QUOTE_FAILURE : Rule.DOUBLE_QUOTE_FAILURE; - this.addFailure(this.createFailure(position, width, failureMessage)); + const newText = expectedQuoteMark + + text.slice(1, -1).replace(new RegExp(expectedQuoteMark, "g"), `\\${expectedQuoteMark}`) + + expectedQuoteMark; + + const fix = new Lint.Fix(Rule.metadata.ruleName, [ new Lint.Replacement(position, width, newText) ]); + this.addFailure(this.createFailure(position, width, failureMessage, fix)); } } diff --git a/test/rules/quotemark/double-avoid-escape/test.ts.fix b/test/rules/quotemark/double-avoid-escape/test.ts.fix new file mode 100644 index 00000000000..a5251c3e457 --- /dev/null +++ b/test/rules/quotemark/double-avoid-escape/test.ts.fix @@ -0,0 +1,4 @@ +var single = "single"; + var doublee = "married"; +var singleWithinDouble = "'singleWithinDouble'"; +var doubleWithinSingle = '"doubleWithinSingle"'; diff --git a/test/rules/quotemark/double/test.ts.fix b/test/rules/quotemark/double/test.ts.fix new file mode 100644 index 00000000000..40f8455f047 --- /dev/null +++ b/test/rules/quotemark/double/test.ts.fix @@ -0,0 +1,4 @@ +var single = "single"; + var doublee = "married"; +var singleWithinDouble = "'singleWithinDouble'"; +var doubleWithinSingle = "\"doubleWithinSingle\""; diff --git a/test/rules/quotemark/jsx-double/test.tsx.fix b/test/rules/quotemark/jsx-double/test.tsx.fix new file mode 100644 index 00000000000..984fbc4ac7d --- /dev/null +++ b/test/rules/quotemark/jsx-double/test.tsx.fix @@ -0,0 +1,4 @@ +import * as React from 'react'; + +export const a = ( +
diff --git a/test/rules/quotemark/jsx-single/test.tsx.fix b/test/rules/quotemark/jsx-single/test.tsx.fix new file mode 100644 index 00000000000..3c2189fc342 --- /dev/null +++ b/test/rules/quotemark/jsx-single/test.tsx.fix @@ -0,0 +1,4 @@ +import * as React from "react"; + +export const a = ( +
diff --git a/test/rules/quotemark/single-avoid-escape/test.ts.fix b/test/rules/quotemark/single-avoid-escape/test.ts.fix new file mode 100644 index 00000000000..b02af962658 --- /dev/null +++ b/test/rules/quotemark/single-avoid-escape/test.ts.fix @@ -0,0 +1,4 @@ +var single = 'single'; + var doublee = 'married'; +var singleWithinDouble = "'singleWithinDouble'"; +var doubleWithinSingle = '"doubleWithinSingle"'; diff --git a/test/rules/quotemark/single/test.ts.fix b/test/rules/quotemark/single/test.ts.fix new file mode 100644 index 00000000000..b73ff7f5bac --- /dev/null +++ b/test/rules/quotemark/single/test.ts.fix @@ -0,0 +1,4 @@ +var single = 'single'; + var doublee = 'married'; +var singleWithinDouble = '\'singleWithinDouble\''; +var doubleWithinSingle = '"doubleWithinSingle"'; From 2b09bb2a3797cbf501e9fe37d5aeafad0888d518 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Sun, 27 Nov 2016 11:20:13 -0500 Subject: [PATCH 16/48] Fix merge issue for `promise-function-async` (#1792) --- src/rules/promiseFunctionAsyncRule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts index a058b686a44..7834d95e9ce 100644 --- a/src/rules/promiseFunctionAsyncRule.ts +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -40,8 +40,8 @@ export class Rule extends Lint.Rules.TypedRule { public static FAILURE_STRING = "functions that return promises must be async"; - public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - return this.applyWithWalker(new PromiseAsyncWalker(sourceFile, this.getOptions(), program)); + public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { + return this.applyWithWalker(new PromiseAsyncWalker(sourceFile, this.getOptions(), langSvc.getProgram())); } } From 94d62fff80a811229ae3a3b608daaced5b1a5b7e Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Mon, 28 Nov 2016 12:00:16 -0500 Subject: [PATCH 17/48] Code style fixes in promiseFunctionAsyncRule (#1793) To promote readability --- src/rules/promiseFunctionAsyncRule.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts index 7834d95e9ce..4582d11bdaf 100644 --- a/src/rules/promiseFunctionAsyncRule.ts +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -47,43 +47,39 @@ export class Rule extends Lint.Rules.TypedRule { class PromiseAsyncWalker extends Lint.ProgramAwareRuleWalker { public visitArrowFunction(node: ts.ArrowFunction) { - this.test(node); + this.handleDeclaration(node); super.visitArrowFunction(node); } public visitFunctionDeclaration(node: ts.FunctionDeclaration) { - this.test(node); + this.handleDeclaration(node); super.visitFunctionDeclaration(node); } public visitFunctionExpression(node: ts.FunctionExpression) { - this.test(node); + this.handleDeclaration(node); super.visitFunctionExpression(node); } public visitMethodDeclaration(node: ts.MethodDeclaration) { - this.test(node); + this.handleDeclaration(node); super.visitMethodDeclaration(node); } - private test(node: ts.SignatureDeclaration & { body?: ts.Node}) { + private handleDeclaration(node: ts.SignatureDeclaration & { body?: ts.Node }) { const tc = this.getTypeChecker(); - const signature = tc.getTypeAtLocation(node).getCallSignatures()[0]; const returnType = tc.typeToString(tc.getReturnTypeOfSignature(signature)); const isAsync = Lint.hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword); const isPromise = returnType.indexOf("Promise<") === 0; - const signatureEnd = node.body ? - node.body.getStart() - node.getStart() - 1 : - node.getWidth() - ; + const signatureEnd = node.body != null + ? node.body.getStart() - node.getStart() - 1 + : node.getWidth(); - if (isAsync || !isPromise) { - return; + if (isPromise && !isAsync) { + this.addFailure(this.createFailure(node.getStart(), signatureEnd, Rule.FAILURE_STRING)); } - - this.addFailure(this.createFailure(node.getStart(), signatureEnd, Rule.FAILURE_STRING)); } } From 3e7d130d8b7b6c3262348a9079d8e4cf7de88864 Mon Sep 17 00:00:00 2001 From: Art Chaidarun Date: Mon, 28 Nov 2016 15:48:07 -0500 Subject: [PATCH 18/48] Fix typo in changelog (#1797) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 849622ab17d..30242ae6abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,7 +68,7 @@ v4.0.0-dev.1 * [new-fixer] `semicolon` auto fixed (#1423) * [new-rule] `max-classes-per-file` rule added (#1666) * [new-rule-option] `no-consecutive-blank-lines` rule now accepts a number value indicating max blank lines (#1650) -* [new-rule-option] `ordered-inputs` rule option `input-sources-order` accepts value `any` (#1602) +* [new-rule-option] `ordered-imports` rule option `import-sources-order` accepts value `any` (#1602) * [bugfix] `no-empty` rule fixed when parameter has readonly modifier * [bugfix] `no-namespace` rule: do not flag nested or .d.ts namespaces (#1571) From 3e9998e619664a4520db711cf72b32a21a024385 Mon Sep 17 00:00:00 2001 From: Timothy Slatcher Date: Tue, 29 Nov 2016 06:56:00 -0800 Subject: [PATCH 19/48] Fix adjacentOverloadSignatures for constructors (#1800) --- src/rules/adjacentOverloadSignaturesRule.ts | 5 ++++- .../adjacent-overload-signatures/test.ts.lint | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index 918aab31031..cd4c74fb519 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -116,7 +116,10 @@ function isLiteralExpression(node: ts.Node): node is ts.LiteralExpression { function getTextOfPropertyName(node: ts.InterfaceDeclaration | ts.TypeElement | ts.ClassElement): string { let nameText: string; if (node.name == null) { - return null; + if (node.kind === ts.SyntaxKind.Constructor) { + return "constructor"; + } + return undefined; } switch (node.name.kind) { case ts.SyntaxKind.Identifier: diff --git a/test/rules/adjacent-overload-signatures/test.ts.lint b/test/rules/adjacent-overload-signatures/test.ts.lint index de6a26b2f28..e7d3286f77f 100644 --- a/test/rules/adjacent-overload-signatures/test.ts.lint +++ b/test/rules/adjacent-overload-signatures/test.ts.lint @@ -37,7 +37,6 @@ interface i6 { toString(): string; } - // bad interface b1 { @@ -114,6 +113,17 @@ declare namespace N { class Foo { public static bar() {} - constructor() {} + private constructor() {} + public bar() {} + public constructor(foo: any) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'constructor' signatures should be adjacent] +} + +// A semi-colon on its own is a statement with no name +class Bar { + private constructor() {} + ; public bar() {} -} \ No newline at end of file + ; +} + From a327148fa5932b4289cc7bbe28cdb477a38ad852 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Wed, 30 Nov 2016 13:54:36 -0500 Subject: [PATCH 20/48] add `prefer-const` rule (#1801) --- src/language/utils.ts | 7 + .../walker/blockScopeAwareRuleWalker.ts | 20 +- src/language/walker/scopeAwareRuleWalker.ts | 8 +- src/language/walker/syntaxWalker.ts | 16 ++ src/rules/preferConstRule.ts | 208 ++++++++++++++++++ test/rules/prefer-const/test.ts.fix | 115 ++++++++++ test/rules/prefer-const/test.ts.lint | 137 ++++++++++++ test/rules/prefer-const/tslint.json | 5 + 8 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 src/rules/preferConstRule.ts create mode 100644 test/rules/prefer-const/test.ts.fix create mode 100644 test/rules/prefer-const/test.ts.lint create mode 100644 test/rules/prefer-const/tslint.json diff --git a/src/language/utils.ts b/src/language/utils.ts index 707f6b52b5e..4d34e645fbd 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -143,3 +143,10 @@ export function isNestedModuleDeclaration(decl: ts.ModuleDeclaration) { // nodes return decl.name.pos === decl.pos; } + +export function unwrapParentheses(node: ts.Expression) { + while (node.kind === ts.SyntaxKind.ParenthesizedExpression) { + node = (node as ts.ParenthesizedExpression).expression; + } + return node; +} diff --git a/src/language/walker/blockScopeAwareRuleWalker.ts b/src/language/walker/blockScopeAwareRuleWalker.ts index e1e17afea24..eb3fa438329 100644 --- a/src/language/walker/blockScopeAwareRuleWalker.ts +++ b/src/language/walker/blockScopeAwareRuleWalker.ts @@ -29,25 +29,29 @@ export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalk constructor(sourceFile: ts.SourceFile, options?: any) { super(sourceFile, options); - // initialize stack with global scope - this.blockScopeStack = [this.createBlockScope()]; + this.blockScopeStack = []; } public abstract createBlockScope(): U; + // get all block scopes available at this depth + public getAllBlockScopes(): U[] { + return this.blockScopeStack; + } + public getCurrentBlockScope(): U { return this.blockScopeStack[this.blockScopeStack.length - 1]; } + public getCurrentBlockDepth(): number { + return this.blockScopeStack.length; + } + // callback notifier when a block scope begins public onBlockScopeStart() { return; } - public getCurrentBlockDepth(): number { - return this.blockScopeStack.length; - } - // callback notifier when a block scope ends public onBlockScopeEnd() { return; @@ -58,13 +62,13 @@ export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalk if (isNewBlockScope) { this.blockScopeStack.push(this.createBlockScope()); + this.onBlockScopeStart(); } - this.onBlockScopeStart(); super.visitNode(node); - this.onBlockScopeEnd(); if (isNewBlockScope) { + this.onBlockScopeEnd(); this.blockScopeStack.pop(); } } diff --git a/src/language/walker/scopeAwareRuleWalker.ts b/src/language/walker/scopeAwareRuleWalker.ts index 0c2b988592d..e1cd6c425bd 100644 --- a/src/language/walker/scopeAwareRuleWalker.ts +++ b/src/language/walker/scopeAwareRuleWalker.ts @@ -25,8 +25,7 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { constructor(sourceFile: ts.SourceFile, options?: any) { super(sourceFile, options); - // initialize stack with global scope - this.scopeStack = [this.createScope(sourceFile)]; + this.scopeStack = []; } public abstract createScope(node: ts.Node): T; @@ -59,19 +58,20 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { if (isNewScope) { this.scopeStack.push(this.createScope(node)); + this.onScopeStart(); } - this.onScopeStart(); super.visitNode(node); - this.onScopeEnd(); if (isNewScope) { + this.onScopeEnd(); this.scopeStack.pop(); } } protected isScopeBoundary(node: ts.Node): boolean { return node.kind === ts.SyntaxKind.FunctionDeclaration + || node.kind === ts.SyntaxKind.SourceFile || node.kind === ts.SyntaxKind.FunctionExpression || node.kind === ts.SyntaxKind.PropertyAssignment || node.kind === ts.SyntaxKind.ShorthandPropertyAssignment diff --git a/src/language/walker/syntaxWalker.ts b/src/language/walker/syntaxWalker.ts index 61470816f16..46dc959f5d8 100644 --- a/src/language/walker/syntaxWalker.ts +++ b/src/language/walker/syntaxWalker.ts @@ -118,6 +118,10 @@ export class SyntaxWalker { this.walkChildren(node); } + protected visitEndOfFileToken(node: ts.Node) { + this.walkChildren(node); + } + protected visitEnumDeclaration(node: ts.EnumDeclaration) { this.walkChildren(node); } @@ -322,6 +326,10 @@ export class SyntaxWalker { this.walkChildren(node); } + protected visitVariableDeclarationList(node: ts.VariableDeclarationList) { + this.walkChildren(node); + } + protected visitVariableStatement(node: ts.VariableStatement) { this.walkChildren(node); } @@ -432,6 +440,10 @@ export class SyntaxWalker { this.visitElementAccessExpression( node); break; + case ts.SyntaxKind.EndOfFileToken: + this.visitEndOfFileToken(node); + break; + case ts.SyntaxKind.EnumDeclaration: this.visitEnumDeclaration( node); break; @@ -640,6 +652,10 @@ export class SyntaxWalker { this.visitVariableDeclaration( node); break; + case ts.SyntaxKind.VariableDeclarationList: + this.visitVariableDeclarationList( node); + break; + case ts.SyntaxKind.VariableStatement: this.visitVariableStatement( node); break; diff --git a/src/rules/preferConstRule.ts b/src/rules/preferConstRule.ts new file mode 100644 index 00000000000..d3454433419 --- /dev/null +++ b/src/rules/preferConstRule.ts @@ -0,0 +1,208 @@ +/** + * @license + * Copyright 2013 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 * as Lint from "../index"; +import {isNodeFlagSet, unwrapParentheses} from "../language/utils"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "prefer-const", + description: "Requires that variable declarations use `const` instead of `let` if possible.", + descriptionDetails: Lint.Utils.dedent` + If a variable is only assigned to once when it is declared, it should be declared using 'const'`, + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "maintainability", + typescriptOnly: false, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING_FACTORY = (identifier: string) => { + return `Identifier '${identifier}' is never reassigned; use 'const' instead of 'let'.`; + } + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const preferConstWalker = new PreferConstWalker(sourceFile, this.getOptions()); + return this.applyWithWalker(preferConstWalker); + } +} + +class PreferConstWalker extends Lint.BlockScopeAwareRuleWalker<{}, ScopeInfo> { + public createScope() { + return {}; + } + + public createBlockScope() { + return new ScopeInfo(); + } + + public onBlockScopeEnd() { + const seenLetStatements: { [startPosition: string]: boolean } = {}; + for (const usage of this.getCurrentBlockScope().getConstCandiates()) { + let fix: Lint.Fix; + if (!usage.reassignedSibling && !seenLetStatements[usage.letStatement.getStart().toString()]) { + // only fix if all variables in the `let` statement can use `const` + const replacement = new Lint.Replacement(usage.letStatement.getStart(), "let".length, "const"); + fix = new Lint.Fix(Rule.metadata.ruleName, [replacement]); + seenLetStatements[usage.letStatement.getStart().toString()] = true; + } + this.addFailure(this.createFailure( + usage.identifier.getStart(), + usage.identifier.getWidth(), + Rule.FAILURE_STRING_FACTORY(usage.identifier.text), + fix, + )); + } + } + + protected visitBinaryExpression(node: ts.BinaryExpression) { + if (isAssignmentOperator(node.operatorToken)) { + this.handleLHSExpression(node.left); + } + super.visitBinaryExpression(node); + } + + protected visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression) { + this.handleUnaryExpression(node); + super.visitPrefixUnaryExpression(node); + } + + protected visitPostfixUnaryExpression(node: ts.PostfixUnaryExpression) { + this.handleUnaryExpression(node); + super.visitPostfixUnaryExpression(node); + } + + protected visitVariableDeclaration(node: ts.VariableDeclaration) { + this.getCurrentBlockScope().currentVariableDeclaration = node; + super.visitVariableDeclaration(node); + this.getCurrentBlockScope().currentVariableDeclaration = null; + } + + protected visitIdentifier(node: ts.Identifier) { + if (this.getCurrentBlockScope().currentVariableDeclaration != null) { + const declarationList = this.getCurrentBlockScope().currentVariableDeclaration.parent; + if (isNodeFlagSet(declarationList, ts.NodeFlags.Let) && !isNodeFlagSet(declarationList.parent, ts.NodeFlags.Export)) { + if (this.isVariableDeclaration(node)) { + this.getCurrentBlockScope().addVariable(node, declarationList); + } + } + } + super.visitIdentifier(node); + } + + private handleLHSExpression(node: ts.Expression) { + node = unwrapParentheses(node); + if (node.kind === ts.SyntaxKind.Identifier) { + this.markAssignment(node as ts.Identifier); + } + } + + private handleUnaryExpression(node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression) { + if (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) { + this.handleLHSExpression(node.operand); + } + } + + private isVariableDeclaration(node: ts.Identifier) { + if (this.getCurrentBlockScope().currentVariableDeclaration != null) { + // `isBindingElementDeclaration` differentiates between non-variable binding elements and variable binding elements + // for example in `let {a: {b}} = {a: {b: 1}}`, `a` is a non-variable and the 1st `b` is a variable + const isBindingElementDeclaration = node.parent.kind === ts.SyntaxKind.BindingElement + && node.parent.getText() === node.getText(); + const isSimpleVariableDeclaration = node.parent.kind === ts.SyntaxKind.VariableDeclaration; + // differentiates between the left and right hand side of a declaration + const inVariableDeclaration = this.getCurrentBlockScope().currentVariableDeclaration.name.getEnd() >= node.getEnd(); + return inVariableDeclaration && (isBindingElementDeclaration || isSimpleVariableDeclaration); + } + return false; + } + + private markAssignment(identifier: ts.Identifier) { + const allBlockScopes = this.getAllBlockScopes(); + // look through block scopes from local -> global + for (let i = allBlockScopes.length - 1; i >= 0; i--) { + if (allBlockScopes[i].incrementVariableUsage(identifier.text)) { + break; + } + } + } +} + +interface IConstCandidate { + letStatement: ts.VariableDeclarationList; + identifier: ts.Identifier; + // whether or not a different variable declaration that shares the same `let` statement is ever reassigned + reassignedSibling: boolean; +} + +class ScopeInfo { + public currentVariableDeclaration: ts.VariableDeclaration; + + private identifierUsages: { + [varName: string]: { + letStatement: ts.VariableDeclarationList, + identifier: ts.Identifier, + usageCount: number, + }, + } = {}; + // variable names grouped by common `let` statements + private sharedLetSets: {[letStartIndex: string]: string[]} = {}; + + public addVariable(identifier: ts.Identifier, letStatement: ts.VariableDeclarationList) { + this.identifierUsages[identifier.text] = { letStatement, identifier, usageCount: 0 }; + const letSetKey = letStatement.getStart().toString(); + if (this.sharedLetSets[letSetKey] == null) { + this.sharedLetSets[letSetKey] = []; + } + this.sharedLetSets[letSetKey].push(identifier.text); + } + + public getConstCandiates() { + const constCandidates: IConstCandidate[] = []; + for (const letSetKey of Object.keys(this.sharedLetSets)) { + const variableNames = this.sharedLetSets[letSetKey]; + const anyReassigned = variableNames.some((key) => this.identifierUsages[key].usageCount > 0); + for (const variableName of variableNames) { + const usage = this.identifierUsages[variableName]; + if (usage.usageCount === 0) { + constCandidates.push({ + identifier: usage.identifier, + letStatement: usage.letStatement, + reassignedSibling: anyReassigned, + }); + } + } + } + return constCandidates; + } + + public incrementVariableUsage(varName: string) { + if (this.identifierUsages[varName] != null) { + this.identifierUsages[varName].usageCount++; + return true; + } + return false; + } +} + +function isAssignmentOperator(token: ts.Node) { + return token.kind >= ts.SyntaxKind.FirstAssignment + && token.kind <= ts.SyntaxKind.LastAssignment; +} diff --git a/test/rules/prefer-const/test.ts.fix b/test/rules/prefer-const/test.ts.fix new file mode 100644 index 00000000000..50943d7f3f7 --- /dev/null +++ b/test/rules/prefer-const/test.ts.fix @@ -0,0 +1,115 @@ +const a = 1; + +// different reassignments +let b = 1; +b = 2; +let b1 = 1; +b1++; +let b2 = 1; +b2--; +let b3 = 1; +b3 /= 2; +let b4 = 1; +--b4; + +// basic failure +const c = 1; // failure + +// multiple declarations +const c5 = 5, c6 = 6; +const c1 = 1, c2 = 2; // 2 failures +let c3 = 1, c4 = 2; // 1 failure +c3 = 4; + +// destructuring +let {h, i} = {h: 1, i: 1}; // failure for 'h' +let [j, k] = [1, 1]; // failure for 'j' +let [x1, x3] = [1, 2], [x2] = [3]; // failure for x1, x3 +let {a: {b: {q}, c: {r}}} = { a: { b: { q: 3 }, c: { r: 2 } } }; // failure for 'r' +i = 2; +k = 2; +q = 4; +x2 = 5; + +// functions +function myFunc(d: number, e: number) { + const f = 1; // failure + const g = 1; + d = 2; +} +function myFunc2() { + let [l, m] = [1, 1]; // failure for 'l' + m = 2; + return l; +} + +// for-of +for (const n of [1, 1]) { // failure for 'n' + console.log(n); +} +for (let {o, p} of [{1, 1}, {1, 1}]) { // failure for 'o' + console.log(o); + p = 2; +} + +// for loop +for (let i1 = 0; i1 < 4; i1++) { +} +for (const i2 = 0;;) { // failure +} +for (const i2 = 0;;) { // failure +} + +// scope +let sc = 0; +const sc1 = 0; +let sc2 = 0; +let sc3 = 0; +{ + sc = 1; +} +for(;;) { + sc2 = 3; +} +class MyClass { + private classVar = 5; + public Increment() { + let sc4 = 0; + const sc5 = 0; // error + this.classVar++; + sc3++; + sc4++; + } +} + +// separate declaration and assignment (no error) +let x: number; +x = 5; + +// ignores RHS of declaration +const usedAsRHS = 3; +const LHS: number | string = usedAsRHS; + +// handle nested declaration +const nest = () => { + const a = 4; + let b = 0; + const c = 1; + b = 2; + return a + b + c; +}; + +const nest2 = () => { + try { + } catch(error) { + } +} + +// export +export let x = 4; + +// array +const arr = []; +arr.push(0); +arr[1] = 1; +arr.length = 1; diff --git a/test/rules/prefer-const/test.ts.lint b/test/rules/prefer-const/test.ts.lint new file mode 100644 index 00000000000..04b77ad1d0d --- /dev/null +++ b/test/rules/prefer-const/test.ts.lint @@ -0,0 +1,137 @@ +const a = 1; + +// different reassignments +let b = 1; +b = 2; +let b1 = 1; +b1++; +let b2 = 1; +b2--; +let b3 = 1; +b3 /= 2; +let b4 = 1; +--b4; + +// basic failure +let c = 1; // failure + ~ [Identifier 'c' is never reassigned; use 'const' instead of 'let'.] + +// multiple declarations +const c5 = 5, c6 = 6; +let c1 = 1, c2 = 2; // 2 failures + ~~ [Identifier 'c1' is never reassigned; use 'const' instead of 'let'.] + ~~ [Identifier 'c2' is never reassigned; use 'const' instead of 'let'.] +let c3 = 1, c4 = 2; // 1 failure + ~~ [Identifier 'c4' is never reassigned; use 'const' instead of 'let'.] +c3 = 4; + +// destructuring +let {h, i} = {h: 1, i: 1}; // failure for 'h' + ~ [Identifier 'h' is never reassigned; use 'const' instead of 'let'.] +let [j, k] = [1, 1]; // failure for 'j' + ~ [Identifier 'j' is never reassigned; use 'const' instead of 'let'.] +let [x1, x3] = [1, 2], [x2] = [3]; // failure for x1, x3 + ~~ [Identifier 'x1' is never reassigned; use 'const' instead of 'let'.] + ~~ [Identifier 'x3' is never reassigned; use 'const' instead of 'let'.] +let {a: {b: {q}, c: {r}}} = { a: { b: { q: 3 }, c: { r: 2 } } }; // failure for 'r' + ~ [Identifier 'r' is never reassigned; use 'const' instead of 'let'.] +i = 2; +k = 2; +q = 4; +x2 = 5; + +// functions +function myFunc(d: number, e: number) { + let f = 1; // failure + ~ [Identifier 'f' is never reassigned; use 'const' instead of 'let'.] + const g = 1; + d = 2; +} +function myFunc2() { + let [l, m] = [1, 1]; // failure for 'l' + ~ [Identifier 'l' is never reassigned; use 'const' instead of 'let'.] + m = 2; + return l; +} + +// for-of +for (let n of [1, 1]) { // failure for 'n' + ~ [Identifier 'n' is never reassigned; use 'const' instead of 'let'.] + console.log(n); +} +for (let {o, p} of [{1, 1}, {1, 1}]) { // failure for 'o' + ~ [Identifier 'o' is never reassigned; use 'const' instead of 'let'.] + console.log(o); + p = 2; +} + +// for loop +for (let i1 = 0; i1 < 4; i1++) { +} +for (let i2 = 0;;) { // failure + ~~ [Identifier 'i2' is never reassigned; use 'const' instead of 'let'.] +} +for (const i2 = 0;;) { // failure +} + +// scope +let sc = 0; +let sc1 = 0; + ~~~ [Identifier 'sc1' is never reassigned; use 'const' instead of 'let'.] +let sc2 = 0; +let sc3 = 0; +{ + sc = 1; +} +for(;;) { + sc2 = 3; +} +class MyClass { + private classVar = 5; + public Increment() { + let sc4 = 0; + let sc5 = 0; // error + ~~~ [Identifier 'sc5' is never reassigned; use 'const' instead of 'let'.] + this.classVar++; + sc3++; + sc4++; + } +} + +// separate declaration and assignment (no error) +let x: number; +x = 5; + +// ignores RHS of declaration +let usedAsRHS = 3; + ~~~~~~~~~ [Identifier 'usedAsRHS' is never reassigned; use 'const' instead of 'let'.] +let LHS: number | string = usedAsRHS; + ~~~ [Identifier 'LHS' is never reassigned; use 'const' instead of 'let'.] + +// handle nested declaration +let nest = () => { + ~~~~ [Identifier 'nest' is never reassigned; use 'const' instead of 'let'.] + const a = 4; + let b = 0; + let c = 1; + ~ [Identifier 'c' is never reassigned; use 'const' instead of 'let'.] + b = 2; + return a + b + c; +}; + +let nest2 = () => { + ~~~~~ [Identifier 'nest2' is never reassigned; use 'const' instead of 'let'.] + try { + } catch(error) { + } +} + +// export +export let x = 4; + +// array +let arr = []; + ~~~ [Identifier 'arr' is never reassigned; use 'const' instead of 'let'.] +arr.push(0); +arr[1] = 1; +arr.length = 1; diff --git a/test/rules/prefer-const/tslint.json b/test/rules/prefer-const/tslint.json new file mode 100644 index 00000000000..369865b3ae9 --- /dev/null +++ b/test/rules/prefer-const/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "prefer-const": true + } +} From dcc4518781421ad21cb677acc2dd294916439260 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 1 Dec 2016 02:06:50 -0500 Subject: [PATCH 21/48] Treat `SourceFile` as scope boundary only if it is an external module (#1805) --- src/language/walker/blockScopeAwareRuleWalker.ts | 3 ++- src/language/walker/scopeAwareRuleWalker.ts | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/language/walker/blockScopeAwareRuleWalker.ts b/src/language/walker/blockScopeAwareRuleWalker.ts index eb3fa438329..82903c8b0fe 100644 --- a/src/language/walker/blockScopeAwareRuleWalker.ts +++ b/src/language/walker/blockScopeAwareRuleWalker.ts @@ -29,7 +29,8 @@ export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalk constructor(sourceFile: ts.SourceFile, options?: any) { super(sourceFile, options); - this.blockScopeStack = []; + // initialize with global scope if file is not a module + this.blockScopeStack = this.fileIsModule ? [] : [this.createBlockScope()]; } public abstract createBlockScope(): U; diff --git a/src/language/walker/scopeAwareRuleWalker.ts b/src/language/walker/scopeAwareRuleWalker.ts index e1cd6c425bd..aa01d4af19e 100644 --- a/src/language/walker/scopeAwareRuleWalker.ts +++ b/src/language/walker/scopeAwareRuleWalker.ts @@ -20,12 +20,16 @@ import * as ts from "typescript"; 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.scopeStack = []; + this.fileIsModule = ts.isExternalModule(sourceFile); + + // initialize with global scope if file is not a module + this.scopeStack = this.fileIsModule ? [] : [this.createScope(sourceFile)]; } public abstract createScope(node: ts.Node): T; @@ -71,7 +75,6 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { protected isScopeBoundary(node: ts.Node): boolean { return node.kind === ts.SyntaxKind.FunctionDeclaration - || node.kind === ts.SyntaxKind.SourceFile || node.kind === ts.SyntaxKind.FunctionExpression || node.kind === ts.SyntaxKind.PropertyAssignment || node.kind === ts.SyntaxKind.ShorthandPropertyAssignment @@ -84,6 +87,7 @@ export abstract class ScopeAwareRuleWalker extends RuleWalker { || node.kind === ts.SyntaxKind.ClassExpression || node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.GetAccessor - || node.kind === ts.SyntaxKind.SetAccessor; + || node.kind === ts.SyntaxKind.SetAccessor + || (node.kind === ts.SyntaxKind.SourceFile && this.fileIsModule); } } From a8d3a43394d7e026853b38b6bb5923404d82cd66 Mon Sep 17 00:00:00 2001 From: Feisal Ahmad Date: Thu, 1 Dec 2016 16:12:49 -0500 Subject: [PATCH 22/48] Support multiple files in checkstyle formatter (#1811) --- src/formatters/checkstyleFormatter.ts | 28 ++++++++++++++------ test/formatters/checkstyleFormatterTests.ts | 29 +++++++++++++++------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/formatters/checkstyleFormatter.ts b/src/formatters/checkstyleFormatter.ts index 3360b409bed..913ff898b84 100644 --- a/src/formatters/checkstyleFormatter.ts +++ b/src/formatters/checkstyleFormatter.ts @@ -26,16 +26,28 @@ export class Formatter extends AbstractFormatter { let output = ''; if (failures.length) { - output += ``; - for (let failure of failures) { - output += ` { + return a.getFileName().localeCompare(b.getFileName()); + }); + let previousFilename: string = null; + for (let failure of failuresSorted) { + if (failure.getFileName() !== previousFilename) { + if (previousFilename) { + output += ""; + } + previousFilename = failure.getFileName(); + output += ""; + } + output += "dotdot - output += `source="failure.tslint.${this.escapeXml(failure.getRuleName())}" />`; + output += "source=\"failure.tslint." + this.escapeXml(failure.getRuleName()) + "\" />"; + } + if (previousFilename) { + output += ""; } - output += ""; } output += ""; diff --git a/test/formatters/checkstyleFormatterTests.ts b/test/formatters/checkstyleFormatterTests.ts index c9428068ce7..f0ebf7054f4 100644 --- a/test/formatters/checkstyleFormatterTests.ts +++ b/test/formatters/checkstyleFormatterTests.ts @@ -3,27 +3,40 @@ import * as ts from "typescript"; import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("Checkstyle Formatter", () => { - const TEST_FILE = "formatters/pmdFormatter.test.ts"; // reuse existing sample file - let sourceFile: ts.SourceFile; + const TEST_FILE_1 = "formatters/jsonFormatter.test.ts"; // reuse existing sample file + const TEST_FILE_2 = "formatters/pmdFormatter.test.ts"; // reuse existing sample file + let sourceFile1: ts.SourceFile; + let sourceFile2: ts.SourceFile; let formatter: IFormatter; before(() => { const Formatter = TestUtils.getFormatter("checkstyle"); - sourceFile = TestUtils.getSourceFile(TEST_FILE); + sourceFile1 = TestUtils.getSourceFile(TEST_FILE_1); + sourceFile2 = TestUtils.getSourceFile(TEST_FILE_2); formatter = new Formatter(); }); it("formats failures", () => { - const maxPosition = sourceFile.getFullWidth(); + const maxPosition1 = sourceFile1.getFullWidth(); + const maxPosition2 = sourceFile2.getFullWidth(); const failures = [ - new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), - new RuleFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape"), - new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + new RuleFailure(sourceFile1, 0, 1, "first failure", "first-name"), + new RuleFailure(sourceFile1, 2, 3, "&<>'\" should be escaped", "escape"), + new RuleFailure(sourceFile1, maxPosition1 - 1, maxPosition1, "last failure", "last-name"), + new RuleFailure(sourceFile2, 0, 1, "first failure", "first-name"), + new RuleFailure(sourceFile2, 2, 3, "&<>'\" should be escaped", "escape"), + new RuleFailure(sourceFile2, maxPosition2 - 1, maxPosition2, "last failure", "last-name"), ]; const expectedResult = '' + - `` + + `` + + '' + + '' + + '' + + "" + + `` + '' + '' + From a277873701ee24e75c8a742f83f054914ac20c65 Mon Sep 17 00:00:00 2001 From: wmrowan Date: Fri, 2 Dec 2016 15:23:42 -0800 Subject: [PATCH 23/48] Do not apply trailing comma rule to type parameter lists (#1775) --- src/rules/trailingCommaRule.ts | 1 - .../multiline-always/test.ts.fix | 36 +++++++++---------- .../multiline-always/test.ts.lint | 30 ++++++---------- .../multiline-never/test.ts.lint | 9 ++--- .../singleline-always/test.ts.fix | 22 ++++++------ .../singleline-always/test.ts.lint | 17 +++------ .../singleline-never/test.ts.lint | 9 ++--- 7 files changed, 50 insertions(+), 74 deletions(-) diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 845cf4be0f7..84e949cafa1 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -70,7 +70,6 @@ class TrailingCommaWalker extends Lint.RuleWalker { [ts.SyntaxKind.OpenBraceToken, ts.SyntaxKind.CloseBraceToken], [ts.SyntaxKind.OpenBracketToken, ts.SyntaxKind.CloseBracketToken], [ts.SyntaxKind.OpenParenToken, ts.SyntaxKind.CloseParenToken], - [ts.SyntaxKind.LessThanToken, ts.SyntaxKind.GreaterThanToken], ]; public visitArrayLiteralExpression(node: ts.ArrayLiteralExpression) { diff --git a/test/rules/trailing-comma/multiline-always/test.ts.fix b/test/rules/trailing-comma/multiline-always/test.ts.fix index ca1572a3f90..b51ab4acf7f 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.fix +++ b/test/rules/trailing-comma/multiline-always/test.ts.fix @@ -355,7 +355,7 @@ class Test { foo5< T extends Test<[A, A], [A, A,], A>, - U extends Test, + U extends Test, V extends Test< [ C, @@ -365,8 +365,8 @@ class Test { C, C, ], - C, - >, + C + > >( bar: A, baz: T, @@ -386,7 +386,7 @@ class Test { string, string, ]; - }, + } >() { return 42; } set bar(foo: string) { } @@ -402,29 +402,29 @@ class Test { ) { } } -class Test2 { } +class Test2 { } class Test3< A, B, - C, + C > { } class Test4< A, B, - C, + C > { } interface ITest { new (bar: U): ITest; - new (bar: U, baz: V,): ITest; + new (bar: U, baz: V,): ITest; new < U, - V, + V >( bar: U, baz: U, @@ -432,12 +432,12 @@ interface ITest { ): ITest< U, U, - V, + V >; new < U, - V, + V >( bar: U, baz: U, @@ -446,7 +446,7 @@ interface ITest { ): ITest< U, U, - V, + V >; foo(bar: string, baz: string); @@ -465,7 +465,7 @@ interface ITest { foo5< T extends Test<[A, A], [A, A,], A>, - U extends Test, + U extends Test, V extends Test< [ C, @@ -475,8 +475,8 @@ interface ITest { C, C, ], - C, - >, + C + > >( bar: A, baz: T, @@ -494,7 +494,7 @@ interface ITest { string, string, ]; - }, + } >(); } @@ -503,13 +503,13 @@ interface ITest2 { } interface ITest3< A, B, - C, + C > { } interface ITest4< A, B, - C, + C > { } enum Foo { BAR, BAZ } diff --git a/test/rules/trailing-comma/multiline-always/test.ts.lint b/test/rules/trailing-comma/multiline-always/test.ts.lint index 3f62655e8ab..0747fdcb82f 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.lint +++ b/test/rules/trailing-comma/multiline-always/test.ts.lint @@ -380,7 +380,7 @@ class Test { foo5< T extends Test<[A, A], [A, A,], A>, - U extends Test, + U extends Test, V extends Test< [ C, @@ -392,9 +392,7 @@ class Test { C, ], C - ~ [Missing trailing comma] > - ~ [Missing trailing comma] >( bar: A, baz: T, @@ -414,7 +412,7 @@ class Test { string, string, ]; - }, + } >() { return 42; } set bar(foo: string) { } @@ -431,46 +429,42 @@ class Test { ) { } } -class Test2 { } +class Test2 { } class Test3< A, B, C - ~ [Missing trailing comma] > { } class Test4< A, B, - C, + C > { } interface ITest { new (bar: U): ITest; - new (bar: U, baz: V,): ITest; + new (bar: U, baz: V,): ITest; new < U, V - ~ [Missing trailing comma] >( bar: U, baz: U, - ack: V - ~ [Missing trailing comma] + ack: V, ): ITest< U, U, V - ~ [Missing trailing comma] >; new < U, - V, + V >( bar: U, baz: U, @@ -479,7 +473,7 @@ interface ITest { ): ITest< U, U, - V, + V >; foo(bar: string, baz: string); @@ -499,7 +493,7 @@ interface ITest { foo5< T extends Test<[A, A], [A, A,], A>, - U extends Test, + U extends Test, V extends Test< [ C, @@ -511,9 +505,7 @@ interface ITest { C, ], C - ~ [Missing trailing comma] > - ~ [Missing trailing comma] >( bar: A, baz: T, @@ -533,7 +525,6 @@ interface ITest { ~ [Missing trailing comma] ]; } - ~ [Missing trailing comma] >(); } @@ -543,13 +534,12 @@ interface ITest3< A, B, C - ~ [Missing trailing comma] > { } interface ITest4< A, B, - C, + C > { } enum Foo { BAR, BAZ } diff --git a/test/rules/trailing-comma/multiline-never/test.ts.lint b/test/rules/trailing-comma/multiline-never/test.ts.lint index bc6e2ed78be..40720a39a15 100644 --- a/test/rules/trailing-comma/multiline-never/test.ts.lint +++ b/test/rules/trailing-comma/multiline-never/test.ts.lint @@ -414,8 +414,7 @@ class Test { string, ~ [Unnecessary trailing comma] ]; - }, - ~ [Unnecessary trailing comma] + } >() { return 42; } } @@ -430,8 +429,7 @@ class Test3< class Test4< A, B, - C, - ~ [Unnecessary trailing comma] + C > { } interface ITest { @@ -498,8 +496,7 @@ interface ITest3< interface ITest4< A, B, - C, - ~ [Unnecessary trailing comma] + C > { } enum Foo { BAR, BAZ } diff --git a/test/rules/trailing-comma/singleline-always/test.ts.fix b/test/rules/trailing-comma/singleline-always/test.ts.fix index 77c0559df1d..26ece235b03 100644 --- a/test/rules/trailing-comma/singleline-always/test.ts.fix +++ b/test/rules/trailing-comma/singleline-always/test.ts.fix @@ -312,7 +312,7 @@ var x: { bar: string, }; -class Test { +class Test { constructor(bar: string,) { } @@ -354,8 +354,8 @@ class Test { } foo5< - T extends Test<[A, A,], [A, A,], A,>, - U extends Test, + T extends Test<[A, A,], [A, A,], A>, + U extends Test, V extends Test< [ C, @@ -402,7 +402,7 @@ class Test { ) { } } -class Test2 { } +class Test2 { } class Test3< A, @@ -416,9 +416,9 @@ class Test4< C, > { } -interface ITest { +interface ITest { - new (bar: U,): ITest; + new (bar: U,): ITest; new < U, @@ -435,7 +435,7 @@ interface ITest { new < U, - V, + V >( bar: U, baz: U, @@ -444,7 +444,7 @@ interface ITest { ): ITest< U, U, - V, + V >; foo(bar: string, baz: string,); @@ -462,8 +462,8 @@ interface ITest { ); foo5< - T extends Test<[A, A,], [A, A,], A,>, - U extends Test, + T extends Test<[A, A,], [A, A,], A>, + U extends Test, V extends Test< [ C, @@ -496,7 +496,7 @@ interface ITest { >(); } -interface ITest2 { } +interface ITest2 { } interface ITest3< A, diff --git a/test/rules/trailing-comma/singleline-always/test.ts.lint b/test/rules/trailing-comma/singleline-always/test.ts.lint index dba8dc83e84..6d7d6896713 100644 --- a/test/rules/trailing-comma/singleline-always/test.ts.lint +++ b/test/rules/trailing-comma/singleline-always/test.ts.lint @@ -332,7 +332,6 @@ var x: { }; class Test { - ~ [Missing trailing comma] constructor(bar: string) { } ~ [Missing trailing comma] @@ -378,8 +377,7 @@ class Test { foo5< T extends Test<[A, A], [A, A,], A>, ~ [Missing trailing comma] - ~ [Missing trailing comma] - U extends Test, + U extends Test, V extends Test< [ C, @@ -427,7 +425,7 @@ class Test { ) { } } -class Test2 { } +class Test2 { } class Test3< A, @@ -442,12 +440,9 @@ class Test4< > { } interface ITest { - ~ [Missing trailing comma] new (bar: U): ITest; - ~ [Missing trailing comma] ~ [Missing trailing comma] - ~ [Missing trailing comma] new < U, @@ -464,7 +459,7 @@ interface ITest { new < U, - V, + V >( bar: U, baz: U, @@ -473,7 +468,7 @@ interface ITest { ): ITest< U, U, - V, + V >; foo(bar: string, baz: string); @@ -494,8 +489,7 @@ interface ITest { foo5< T extends Test<[A, A], [A, A,], A>, ~ [Missing trailing comma] - ~ [Missing trailing comma] - U extends Test, + U extends Test, V extends Test< [ C, @@ -529,7 +523,6 @@ interface ITest { } interface ITest2 { } - ~ [Missing trailing comma] interface ITest3< A, diff --git a/test/rules/trailing-comma/singleline-never/test.ts.lint b/test/rules/trailing-comma/singleline-never/test.ts.lint index 4c42d04a8d3..463d39f71c1 100644 --- a/test/rules/trailing-comma/singleline-never/test.ts.lint +++ b/test/rules/trailing-comma/singleline-never/test.ts.lint @@ -378,8 +378,7 @@ class Test { foo5< T extends Test<[A, A], [A, A,], A>, ~ [Unnecessary trailing comma] - U extends Test, - ~ [Unnecessary trailing comma] + U extends Test, V extends Test< [ C, @@ -414,8 +413,7 @@ class Test { >() { return 42; } } -class Test2 { } - ~ [Unnecessary trailing comma] +class Test2 { } class Test3< @@ -449,8 +447,7 @@ interface ITest { foo5< T extends Test<[A, A], [A, A,], A>, ~ [Unnecessary trailing comma] - U extends Test, - ~ [Unnecessary trailing comma] + U extends Test, V extends Test< [ C, From 13ecbff305059d8da5353777c5b702f078b8dfae Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Tue, 6 Dec 2016 11:13:05 -0800 Subject: [PATCH 24/48] Add CodeFrame Formatter (#1819) --- docs/_data/formatters.json | 7 ++ docs/_data/rules.json | 25 ++++ docs/formatters/codeFrame/index.html | 22 ++++ docs/usage/cli/index.md | 2 +- src/formatters/codeFrameFormatter.ts | 103 +++++++++++++++ src/formatters/index.ts | 1 + src/language/rule/rule.ts | 6 + src/tslint-cli.ts | 2 +- .../formatters/codeFrameFormatter.test.ts | 9 ++ test/formatters/codeFrameFormatterTests.ts | 119 ++++++++++++++++++ 10 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 docs/formatters/codeFrame/index.html create mode 100644 src/formatters/codeFrameFormatter.ts create mode 100644 test/files/formatters/codeFrameFormatter.test.ts create mode 100644 test/formatters/codeFrameFormatterTests.ts diff --git a/docs/_data/formatters.json b/docs/_data/formatters.json index c3541f5d75c..1617406a14b 100644 --- a/docs/_data/formatters.json +++ b/docs/_data/formatters.json @@ -6,6 +6,13 @@ "sample": "\n\n\n \n \n \n", "consumer": "machine" }, + { + "formatterName": "codeFrame", + "description": "Framed formatter which creates a frame of error code.", + "descriptionDetails": "\nPrints syntax highlighted code in a frame with a pointer to where\nexactly lint error is happening.", + "sample": "\nsrc/components/Payment.tsx\nParentheses are required around the parameters of an arrow function definition (arrow-parens)\n 21 | public componentDidMount() {\n 22 | this.input.focus();\n> 23 | loadStripe().then(Stripe => Stripe.pay());\n | ^\n 24 | }\n 25 |\n 26 | public render() {", + "consumer": "human" + }, { "formatterName": "filesList", "description": "Lists files containing lint errors.", diff --git a/docs/_data/rules.json b/docs/_data/rules.json index 61d5c2d6c0b..e45072f078f 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -1071,6 +1071,18 @@ "type": "style", "typescriptOnly": false }, + { + "ruleName": "prefer-const", + "description": "Requires that variable declarations use `const` instead of `let` if possible.", + "descriptionDetails": "\nIf a variable is only assigned to once when it is declared, it should be declared using 'const'", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "maintainability", + "typescriptOnly": false + }, { "ruleName": "prefer-for-of", "description": "Recommends a 'for-of' loop over a standard 'for' loop if the index is only used to access the array being iterated.", @@ -1083,6 +1095,19 @@ "type": "typescript", "typescriptOnly": false }, + { + "ruleName": "promise-function-async", + "description": "Requires any function or method that returns a promise to be marked async.", + "rationale": "\nEnsures that each function is only capable of 1) returning a rejected promise, or 2)\nthrowing an Error object. In contrast, non-`async` `Promise`-returning functions\nare technically capable of either. This practice removes a requirement for consuming\ncode to handle both cases.\n ", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "typescript", + "typescriptOnly": false, + "requiresTypeInfo": true + }, { "ruleName": "quotemark", "description": "Requires single or double quotes for string literals.", diff --git a/docs/formatters/codeFrame/index.html b/docs/formatters/codeFrame/index.html new file mode 100644 index 00000000000..6d531757ad3 --- /dev/null +++ b/docs/formatters/codeFrame/index.html @@ -0,0 +1,22 @@ +--- +formatterName: codeFrame +description: Framed formatter which creates a frame of error code. +descriptionDetails: |- + + Prints syntax highlighted code in a frame with a pointer to where + exactly lint error is happening. +sample: |- + + src/components/Payment.tsx + Parentheses are required around the parameters of an arrow function definition (arrow-parens) + 21 | public componentDidMount() { + 22 | this.input.focus(); + > 23 | loadStripe().then(Stripe => Stripe.pay()); + | ^ + 24 | } + 25 | + 26 | public render() { +consumer: human +layout: formatter +title: 'Formatter: codeFrame' +--- \ No newline at end of file diff --git a/docs/usage/cli/index.md b/docs/usage/cli/index.md index 6992d54d605..85b5fca9c57 100644 --- a/docs/usage/cli/index.md +++ b/docs/usage/cli/index.md @@ -39,7 +39,7 @@ Options: --project tsconfig.json file -r, --rules-dir rules directory -s, --formatters-dir formatters directory --t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist) [default: "prose"] +-t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist, codeFrame) [default: "prose"] --test test that tslint produces the correct output for the specified directory --type-check enable type checking when linting a project -v, --version current version diff --git a/src/formatters/codeFrameFormatter.ts b/src/formatters/codeFrameFormatter.ts new file mode 100644 index 00000000000..088718caf8d --- /dev/null +++ b/src/formatters/codeFrameFormatter.ts @@ -0,0 +1,103 @@ +/** + * @license + * Copyright 2013 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 {AbstractFormatter} from "../language/formatter/abstractFormatter"; +import {IFormatterMetadata} from "../language/formatter/formatter"; +import {RuleFailure} from "../language/rule/rule"; + +import * as colors from "colors"; +// TODO: once types for babel-code-frame are published, add it as a dependency and use import +// @types/babel-code-frame PR: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/13102 +// tslint:disable-next-line:no-var-requires +const codeFrame = require("babel-code-frame"); + +import * as Utils from "../utils"; + +export class Formatter extends AbstractFormatter { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: IFormatterMetadata = { + formatterName: "codeFrame", + description: "Framed formatter which creates a frame of error code.", + descriptionDetails: Utils.dedent` + Prints syntax highlighted code in a frame with a pointer to where + exactly lint error is happening.`, + sample: Utils.dedent` + src/components/Payment.tsx + Parentheses are required around the parameters of an arrow function definition (arrow-parens) + 21 | public componentDidMount() { + 22 | this.input.focus(); + > 23 | loadStripe().then(Stripe => Stripe.pay()); + | ^ + 24 | } + 25 | + 26 | public render() {`, + consumer: "human", + }; + /* tslint:enable:object-literal-sort-keys */ + + public format(failures: RuleFailure[]): string { + if (typeof failures[0] === "undefined") { + return "\n"; + } + + const outputLines: string[] = []; + + let currentFile: string; + + for (const failure of failures) { + const fileName = failure.getFileName(); + + // Output the name of each file once + if (currentFile !== fileName) { + outputLines.push(""); + outputLines.push(fileName); + currentFile = fileName; + } + + let failureString = failure.getFailure(); + failureString = colors.red(failureString); + + // Rule + let ruleName = failure.getRuleName(); + ruleName = colors.gray(`(${ruleName})`); + + // Frame + const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); + const frame = codeFrame( + failure.getRawLines(), + lineAndCharacter.line + 1, // babel-code-frame is 1 index + lineAndCharacter.character, + { + forceColor: colors.enabled, + highlightCode: true, + }, + ); + + // Ouput + outputLines.push(`${failureString} ${ruleName}`); + outputLines.push(frame); + outputLines.push(""); + } + + // Removes initial blank line + if (outputLines[0] === "") { + outputLines.shift(); + } + + return outputLines.join("\n") + "\n"; + } +} diff --git a/src/formatters/index.ts b/src/formatters/index.ts index 5f64b8dabba..b7ff2e0d76d 100644 --- a/src/formatters/index.ts +++ b/src/formatters/index.ts @@ -21,3 +21,4 @@ export { Formatter as ProseFormatter } from "./proseFormatter"; export { Formatter as VerboseFormatter } from "./verboseFormatter"; export { Formatter as StylishFormatter } from "./stylishFormatter"; export { Formatter as FileslistFormatter } from "./fileslistFormatter"; +export { Formatter as CodeFrameFormatter } from "./codeFrameFormatter"; diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index c44cb3f96e1..f4df1a2ab69 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -190,6 +190,7 @@ export class RuleFailure { private fileName: string; private startPosition: RuleFailurePosition; private endPosition: RuleFailurePosition; + private rawLines: string; constructor(private sourceFile: ts.SourceFile, start: number, @@ -201,6 +202,7 @@ export class RuleFailure { this.fileName = sourceFile.fileName; this.startPosition = this.createFailurePosition(start); this.endPosition = this.createFailurePosition(end); + this.rawLines = sourceFile.text; } public getFileName() { @@ -231,6 +233,10 @@ export class RuleFailure { return this.fix; } + public getRawLines() { + return this.rawLines; + } + public toJson(): any { return { endPosition: this.endPosition.toJson(), diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index c91b8f1b81b..b64bf721117 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -83,7 +83,7 @@ const processed = optimist "t": { alias: "format", default: "prose", - describe: "output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist)", + describe: "output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist, codeFrame)", type: "string", }, "test": { diff --git a/test/files/formatters/codeFrameFormatter.test.ts b/test/files/formatters/codeFrameFormatter.test.ts new file mode 100644 index 00000000000..cf4a1c9fedd --- /dev/null +++ b/test/files/formatters/codeFrameFormatter.test.ts @@ -0,0 +1,9 @@ +module CodeFrameModule { + export class CodeFrameClass { + private name: string; + + constructor(name: string) { + this.name = name; + } + } +} diff --git a/test/formatters/codeFrameFormatterTests.ts b/test/formatters/codeFrameFormatterTests.ts new file mode 100644 index 00000000000..2884a3d3cd7 --- /dev/null +++ b/test/formatters/codeFrameFormatterTests.ts @@ -0,0 +1,119 @@ +/* + * Copyright 2013 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 colors from "colors"; + +import * as ts from "typescript"; + +import {IFormatter, RuleFailure, TestUtils} from "../lint"; + +describe("CodeFrame Formatter", () => { + const TEST_FILE = "formatters/codeFrameFormatter.test.ts"; + let sourceFile: ts.SourceFile; + let formatter: IFormatter; + + before(() => { + const Formatter = TestUtils.getFormatter("codeFrame"); + sourceFile = TestUtils.getSourceFile(TEST_FILE); + formatter = new Formatter(); + }); + + it("formats failures", () => { + const maxPosition = sourceFile.getFullWidth(); + + const failures = [ + new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), + new RuleFailure(sourceFile, 2, 3, "&<>'\" should be escaped", "escape"), + new RuleFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name"), + new RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name"), + ]; + + const expectedResultPlain = + `formatters/codeFrameFormatter.test.ts + first failure (first-name) + > 1 | module CodeFrameModule { + 2 | export class CodeFrameClass { + 3 | private name: string; + 4 | + + &<>'" should be escaped (escape) + > 1 | module CodeFrameModule { + | ^ + 2 | export class CodeFrameClass { + 3 | private name: string; + 4 | + + last failure (last-name) + 7 | } + 8 | } + > 9 | } + | ^ + 10 | + + full failure (full-name) + > 1 | module CodeFrameModule { + 2 | export class CodeFrameClass { + 3 | private name: string; + 4 | + + `; + + const expectedResultColored = + `formatters/codeFrameFormatter.test.ts + \u001b[31mfirst failure\u001b[39m \u001b[90m(first-name)\u001b[39m + \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 1 | \u001b[39mmodule \u001b[33mCodeFrameModule\u001b[39m { + \u001b[90m 2 | \u001b[39m \u001b[36mexport\u001b[39m \u001b[36mclass\u001b[39m \u001b[33mCodeFrameClass\u001b[39m { + \u001b[90m 3 | \u001b[39m private name\u001b[33m:\u001b[39m string\u001b[33m;\u001b[39m + \u001b[90m 4 | \u001b[39m\u001b[0m + + \u001b[31m&<>'\" should be escaped\u001b[39m \u001b[90m(escape)\u001b[39m + \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 1 | \u001b[39mmodule \u001b[33mCodeFrameModule\u001b[39m { + \u001b[90m | \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m + \u001b[90m 2 | \u001b[39m \u001b[36mexport\u001b[39m \u001b[36mclass\u001b[39m \u001b[33mCodeFrameClass\u001b[39m { + \u001b[90m 3 | \u001b[39m private name\u001b[33m:\u001b[39m string\u001b[33m;\u001b[39m + \u001b[90m 4 | \u001b[39m\u001b[0m + + \u001b[31mlast failure\u001b[39m \u001b[90m(last-name)\u001b[39m + \u001b[0m \u001b[90m 7 | \u001b[39m } + \u001b[90m 8 | \u001b[39m } + \u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 9 | \u001b[39m} + \u001b[90m | \u001b[39m\u001b[31m\u001b[1m^\u001b[22m\u001b[39m + \u001b[90m 10 | \u001b[39m\u001b[0m + + \u001b[31mfull failure\u001b[39m \u001b[90m(full-name)\u001b[39m + \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 1 | \u001b[39mmodule \u001b[33mCodeFrameModule\u001b[39m { + \u001b[90m 2 | \u001b[39m \u001b[36mexport\u001b[39m \u001b[36mclass\u001b[39m \u001b[33mCodeFrameClass\u001b[39m { + \u001b[90m 3 | \u001b[39m private name\u001b[33m:\u001b[39m string\u001b[33m;\u001b[39m + \u001b[90m 4 | \u001b[39m\u001b[0m + + `; + + /** Convert output lines to an array of trimmed lines for easier comparing */ + function toTrimmedLines(lines: string): string[] { + return lines.split("\n").map((line) => line.trim()); + } + + const expectedResult = toTrimmedLines(colors.enabled ? expectedResultColored : expectedResultPlain); + const result = toTrimmedLines(formatter.format(failures)); + + assert.deepEqual(result, expectedResult); + }); + + it("handles no failures", () => { + const result = formatter.format([]); + assert.equal(result, "\n"); + }); +}); From 177c6321f38e190d8a7d2bec814f58af17eff3d6 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Tue, 6 Dec 2016 11:36:42 -0800 Subject: [PATCH 25/48] Install babel-code-frame (#1826) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0a5f8b5775a..8c94cda38a0 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "verify": "npm-run-all clean compile lint test docs" }, "dependencies": { + "babel-code-frame": "^6.16.0", "colors": "^1.1.2", "diff": "^3.0.1", "findup-sync": "~0.3.0", From 66c18fd2458145e54f5dcf1adab2371dd8db500b Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Tue, 6 Dec 2016 15:47:52 -0500 Subject: [PATCH 26/48] False positive in `prefer-for-of` rule when element assigned (#1813) --- src/language/utils.ts | 10 +++++++ src/rules/preferConstRule.ts | 9 ++---- src/rules/preferForOfRule.ts | 41 ++++++++++++++++----------- test/rules/prefer-for-of/test.ts.lint | 10 +++++++ 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/language/utils.ts b/src/language/utils.ts index 4d34e645fbd..5b8c2d99387 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -125,6 +125,16 @@ export function someAncestor(node: ts.Node, predicate: (n: ts.Node) => boolean): return predicate(node) || (node.parent && someAncestor(node.parent, predicate)); } +export function isAssignment(node: ts.Node) { + if (node.kind === ts.SyntaxKind.BinaryExpression) { + const binaryExpression = node as ts.BinaryExpression; + return binaryExpression.operatorToken.kind >= ts.SyntaxKind.FirstAssignment + && binaryExpression.operatorToken.kind <= ts.SyntaxKind.LastAssignment; + } else { + return false; + } +} + /** * Bitwise check for node flags. */ diff --git a/src/rules/preferConstRule.ts b/src/rules/preferConstRule.ts index d3454433419..7276054ea99 100644 --- a/src/rules/preferConstRule.ts +++ b/src/rules/preferConstRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; -import {isNodeFlagSet, unwrapParentheses} from "../language/utils"; +import {isAssignment, isNodeFlagSet, unwrapParentheses} from "../language/utils"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -73,7 +73,7 @@ class PreferConstWalker extends Lint.BlockScopeAwareRuleWalker<{}, ScopeInfo> { } protected visitBinaryExpression(node: ts.BinaryExpression) { - if (isAssignmentOperator(node.operatorToken)) { + if (isAssignment(node)) { this.handleLHSExpression(node.left); } super.visitBinaryExpression(node); @@ -201,8 +201,3 @@ class ScopeInfo { return false; } } - -function isAssignmentOperator(token: ts.Node) { - return token.kind >= ts.SyntaxKind.FirstAssignment - && token.kind <= ts.SyntaxKind.LastAssignment; -} diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 7142a298ded..d1ff6c8f9ac 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -17,6 +17,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; +import { isAssignment } from "../language/utils"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -40,9 +41,9 @@ export class Rule extends Lint.Rules.AbstractRule { } interface IIncrementorState { - arrayToken: ts.LeftHandSideExpression; - endIncrementPos: number; - onlyArrayAccess: boolean; + arrayToken: ts.Identifier; + forLoopEndPosition: number; + onlyArrayReadAccess: boolean; } class PreferForOfWalker extends Lint.RuleWalker { @@ -54,7 +55,7 @@ class PreferForOfWalker extends Lint.RuleWalker { this.incrementorMap = {}; } - public visitForStatement(node: ts.ForStatement) { + protected visitForStatement(node: ts.ForStatement) { const arrayNodeInfo = this.getForLoopHeaderInfo(node); let indexVariableName: string; if (arrayNodeInfo != null) { @@ -63,9 +64,9 @@ class PreferForOfWalker extends Lint.RuleWalker { // store `for` loop state this.incrementorMap[indexVariableName] = { - arrayToken, - endIncrementPos: node.incrementor.end, - onlyArrayAccess: true, + arrayToken: arrayToken as ts.Identifier, + forLoopEndPosition: node.incrementor.end + 1, + onlyArrayReadAccess: true, }; } @@ -73,8 +74,8 @@ class PreferForOfWalker extends Lint.RuleWalker { if (indexVariableName != null) { const incrementorState = this.incrementorMap[indexVariableName]; - if (incrementorState.onlyArrayAccess) { - const length = incrementorState.endIncrementPos - node.getStart() + 1; + if (incrementorState.onlyArrayReadAccess) { + const length = incrementorState.forLoopEndPosition - node.getStart(); const failure = this.createFailure(node.getStart(), length, Rule.FAILURE_STRING); this.addFailure(failure); } @@ -84,16 +85,24 @@ class PreferForOfWalker extends Lint.RuleWalker { } } - public visitIdentifier(node: ts.Identifier) { + protected visitIdentifier(node: ts.Identifier) { const incrementorState = this.incrementorMap[node.text]; // check if the identifier is an iterator and is currently in the `for` loop body - if (incrementorState != null && incrementorState.arrayToken != null && incrementorState.endIncrementPos < node.getStart()) { - // mark `onlyArrayAccess` false if iterator is used on anything except the array in the `for` loop header - if (node.parent.kind !== ts.SyntaxKind.ElementAccessExpression - || incrementorState.arrayToken.getText() !== ( node.parent).expression.getText()) { - - incrementorState.onlyArrayAccess = false; + if (incrementorState != null && incrementorState.arrayToken != null && incrementorState.forLoopEndPosition < node.getStart()) { + // check if iterator is used for something other than reading data from array + if (node.parent.kind === ts.SyntaxKind.ElementAccessExpression) { + const elementAccess = node.parent as ts.ElementAccessExpression; + const arrayIdentifier = elementAccess.expression as ts.Identifier; + if (incrementorState.arrayToken.text !== arrayIdentifier.text) { + // iterator used in array other than one iterated over + incrementorState.onlyArrayReadAccess = false; + } else if (isAssignment(elementAccess.parent)) { + // array position is assigned a new value + incrementorState.onlyArrayReadAccess = false; + } + } else { + incrementorState.onlyArrayReadAccess = false; } } super.visitIdentifier(node); diff --git a/test/rules/prefer-for-of/test.ts.lint b/test/rules/prefer-for-of/test.ts.lint index ac67a99dacb..eb12a4b71e1 100644 --- a/test/rules/prefer-for-of/test.ts.lint +++ b/test/rules/prefer-for-of/test.ts.lint @@ -120,6 +120,16 @@ function sampleFunc() { for (var r of arr) { console.log(r); } + + // array element is assigned a new value + for (let x = 0; x < arr.length; x++) { + arr[x] = 4; + } + + // access element other than current + for (let x = 0; x < arr.length; x++) { + let y = arr[x + 1]; + } } [0]: Expected a 'for-of' loop instead of a 'for' loop with this simple iteration From b3b4703858b98edc4805785ba36846d7fa5698fc Mon Sep 17 00:00:00 2001 From: Subhash Sharma Date: Wed, 7 Dec 2016 21:27:48 +0530 Subject: [PATCH 27/48] rule to check empty object type inference (#1821) The `no-inferred-empty-object-type` rule is useful for capturing the errors caused by typescript in places where it assumes {} as the default type since no explicit type has been provided. --- src/language/utils.ts | 9 ++ src/rules/noInferredEmptyObjectTypeRule.ts | 98 +++++++++++++++++++ .../test.ts.lint | 86 ++++++++++++++++ .../no-inferred-empty-object-type/tslint.json | 8 ++ 4 files changed, 201 insertions(+) create mode 100644 src/rules/noInferredEmptyObjectTypeRule.ts create mode 100644 test/rules/no-inferred-empty-object-type/test.ts.lint create mode 100644 test/rules/no-inferred-empty-object-type/tslint.json diff --git a/src/language/utils.ts b/src/language/utils.ts index 5b8c2d99387..3bcdbb40ce9 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -144,6 +144,15 @@ export function isNodeFlagSet(node: ts.Node, flagToCheck: ts.NodeFlags): boolean /* tslint:enable:no-bitwise */ } +/** + * Bitwise check for type flags. + */ +export function isTypeFlagSet(typ: ts.Type, flagToCheck: ts.TypeFlags): boolean { + /* tslint:disable:no-bitwise */ + return (typ.flags & flagToCheck) !== 0; + /* tslint:enable:no-bitwise */ +} + /** * @returns true if decl is a nested module declaration, i.e. represents a segment of a dotted module path. */ diff --git a/src/rules/noInferredEmptyObjectTypeRule.ts b/src/rules/noInferredEmptyObjectTypeRule.ts new file mode 100644 index 00000000000..5c9b392ff73 --- /dev/null +++ b/src/rules/noInferredEmptyObjectTypeRule.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright 2016 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 * as Lint from "../index"; +import * as utils from "../language/utils"; + +export class Rule extends Lint.Rules.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "no-inferred-empty-object-type", + description: "Disallow type inference of {} (empty object type) at function and constructor call sites", + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "functionality", + typescriptOnly: true, + requiresTypeInfo: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static EMPTY_INTERFACE_INSTANCE = "Explicit type parameter needs to be provided to the constructor"; + public static EMPTY_INTERFACE_FUNCTION = "Explicit type parameter needs to be provided to the function call"; + + public applyWithProgram(srcFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { + return this.applyWithWalker(new NoInferredEmptyObjectTypeRule(srcFile, this.getOptions(), langSvc.getProgram())); + } +} + +class NoInferredEmptyObjectTypeRule extends Lint.ProgramAwareRuleWalker { + private checker: ts.TypeChecker; + + constructor(srcFile: ts.SourceFile, lintOptions: Lint.IOptions, program: ts.Program) { + super(srcFile, lintOptions, program); + this.checker = this.getTypeChecker(); + } + + public visitNewExpression(node: ts.NewExpression): void { + let nodeTypeArgs = node.typeArguments; + let isObjectReference = (o: ts.TypeReference) => utils.isTypeFlagSet(o, ts.TypeFlags.Reference); + if (nodeTypeArgs === undefined) { + let objType = this.checker.getTypeAtLocation(node) as ts.TypeReference; + if (isObjectReference(objType) && objType.typeArguments !== undefined) { + let typeArgs = objType.typeArguments as ts.ObjectType[]; + typeArgs.forEach((a) => { + if (this.isEmptyObjectInterface(a)) { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EMPTY_INTERFACE_INSTANCE)); + } + }); + } + } + super.visitNewExpression(node); + } + + public visitCallExpression(node: ts.CallExpression): void { + if (node.typeArguments === undefined) { + let callSig = this.checker.getResolvedSignature(node); + let retType = this.checker.getReturnTypeOfSignature(callSig) as ts.TypeReference; + if (this.isEmptyObjectInterface(retType)) { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EMPTY_INTERFACE_FUNCTION)); + } + } + super.visitCallExpression(node); + } + + private isEmptyObjectInterface(objType: ts.ObjectType): boolean { + let isAnonymous = utils.isTypeFlagSet(objType, ts.TypeFlags.Anonymous); + let hasProblematicCallSignatures = false; + let hasProperties = (objType.getProperties() !== undefined && objType.getProperties().length > 0); + let hasNumberIndexType = objType.getNumberIndexType() !== undefined; + let hasStringIndexType = objType.getStringIndexType() !== undefined; + let callSig = objType.getCallSignatures(); + if (callSig !== undefined && callSig.length > 0) { + let isClean = callSig.every((sig) => { + let csigRetType = this.checker.getReturnTypeOfSignature(sig) as ts.TypeReference; + return this.isEmptyObjectInterface(csigRetType); + }); + if (!isClean) { + hasProblematicCallSignatures = true; + } + } + return (isAnonymous && !hasProblematicCallSignatures && !hasProperties && !hasNumberIndexType && !hasStringIndexType); + } +} diff --git a/test/rules/no-inferred-empty-object-type/test.ts.lint b/test/rules/no-inferred-empty-object-type/test.ts.lint new file mode 100644 index 00000000000..2967deb4193 --- /dev/null +++ b/test/rules/no-inferred-empty-object-type/test.ts.lint @@ -0,0 +1,86 @@ +let s: string; +let n: number; +let o: Object; +let l: { a: number, b: string }; +let t: [number, string]; +let p: Wrapper; +let f: () => void; +type EmptyFunc = () => void; +let voidFunc: () => void; +let stringFunc: () => string; +function WrapperFunc(x?: T): T { + return x; +} +function CurriedFunc(h?: () => T): () => T { + return h; +} +function MultiParamFunction(x?: T, y?: U): T { + return x; +} +class Wrapper { + val: T; +} +class MultiParamsClass { + val1: T; + val2: U; +} + +/* Bad */ +WrapperFunc(); +~~~~~~~~~~~~~ [Explicit type parameter needs to be provided to the function call] +CurriedFunc(); +~~~~~~~~~~~~~ [Explicit type parameter needs to be provided to the function call] +MultiParamFunction(); +~~~~~~~~~~~~~~~~~~~~ [Explicit type parameter needs to be provided to the function call] +new Wrapper(); +~~~~~~~~~~~~~ [Explicit type parameter needs to be provided to the constructor] +new MultiParamsClass(); +~~~~~~~~~~~~~~~~~~~~~~ [Explicit type parameter needs to be provided to the constructor] + +/* Good */ +WrapperFunc(); +WrapperFunc(); +WrapperFunc(); +WrapperFunc(s); +WrapperFunc(n); +WrapperFunc(o); +WrapperFunc(l); +WrapperFunc(t); +WrapperFunc(p); +WrapperFunc(f); +WrapperFunc<() => {}>(); + +WrapperFunc<() => void>(); +WrapperFunc<{ a: number }>(); +WrapperFunc<{ [x: number]: string }>(); +WrapperFunc<{ [x: string]: string }>(); + +CurriedFunc(voidFunc); +CurriedFunc(stringFunc); + +MultiParamFunction(); +MultiParamFunction<{}, number>(); +MultiParamFunction(); +MultiParamFunction<{ a: number }, string>(); +MultiParamFunction<() => {}, () => void>(); +MultiParamFunction<() => void, () => {}>(); +MultiParamFunction<() => void, () => void>(); +MultiParamFunction<{ [x: number]: string }, () => void>(); +MultiParamFunction<{ [x: string]: string }, number>(); + +new Wrapper(); +new Wrapper<() => void>(); +new Wrapper<() => {}>(); +new Wrapper<{ a: number }>(); +new Wrapper<{ [x: number]: string }>(); +new Wrapper<{ [x: string]: string }>(); + +new MultiParamsClass(); +new MultiParamsClass<{}, number>(); +new MultiParamsClass(); +new MultiParamsClass<{ a: number }, string>(); +new MultiParamsClass<() => {}, () => void>(); +new MultiParamsClass<() => void, () => {}>(); +new MultiParamsClass<() => void, () => void>(); +new MultiParamsClass<{ [x: number]: string }, () => void>(); +new MultiParamsClass<{ [x: string]: string }, number>(); diff --git a/test/rules/no-inferred-empty-object-type/tslint.json b/test/rules/no-inferred-empty-object-type/tslint.json new file mode 100644 index 00000000000..46856d2b38a --- /dev/null +++ b/test/rules/no-inferred-empty-object-type/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "no-inferred-empty-object-type": true + } +} From 9b8a837438785d15a11899d716b21a3dfe5eb153 Mon Sep 17 00:00:00 2001 From: Yuichi Nukiyama Date: Thu, 8 Dec 2016 01:22:22 +0900 Subject: [PATCH 28/48] allow empty catch (#1817) --- src/rules/noEmptyRule.ts | 26 ++++++-- .../rules/no-empty/{ => default}/test.js.lint | 14 +++++ test/rules/no-empty/default/test.ts.lint | 61 +++++++++++++++++++ test/rules/no-empty/{ => default}/tslint.json | 0 .../no-empty/ignore-empty-catch/test.js.lint | 60 ++++++++++++++++++ .../{ => ignore-empty-catch}/test.ts.lint | 13 ++++ .../no-empty/ignore-empty-catch/tslint.json | 8 +++ 7 files changed, 178 insertions(+), 4 deletions(-) rename test/rules/no-empty/{ => default}/test.js.lint (81%) create mode 100644 test/rules/no-empty/default/test.ts.lint rename test/rules/no-empty/{ => default}/tslint.json (100%) create mode 100644 test/rules/no-empty/ignore-empty-catch/test.js.lint rename test/rules/no-empty/{ => ignore-empty-catch}/test.ts.lint (85%) create mode 100644 test/rules/no-empty/ignore-empty-catch/tslint.json diff --git a/src/rules/noEmptyRule.ts b/src/rules/noEmptyRule.ts index d6dbbe1b695..f9eaebfd337 100644 --- a/src/rules/noEmptyRule.ts +++ b/src/rules/noEmptyRule.ts @@ -19,6 +19,8 @@ import * as ts from "typescript"; import * as Lint from "../index"; +const OPTION_IGNORE_CATCH = "allow-empty-catch"; + export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -26,9 +28,20 @@ export class Rule extends Lint.Rules.AbstractRule { description: "Disallows empty blocks.", descriptionDetails: "Blocks with a comment inside are not considered empty.", rationale: "Empty blocks are often indicators of missing code.", - optionsDescription: "Not configurable.", - options: null, - optionExamples: ["true"], + optionsDescription: Lint.Utils.dedent` + One argument may be optionally provided: + + * \`${OPTION_IGNORE_CATCH}\` allows empty catch clause.`, + options: { + type: "array", + items: { + type: "string", + enum: [OPTION_IGNORE_CATCH], + }, + minLength: 0, + maxLength: 1, + }, + optionExamples: ["true", `[true, "${OPTION_IGNORE_CATCH}"]`], type: "functionality", typescriptOnly: false, }; @@ -51,9 +64,14 @@ class BlockWalker extends Lint.RuleWalker { const hasCommentAfter = ts.getTrailingCommentRanges(sourceFileText, openBrace.getEnd()) != null; const hasCommentBefore = ts.getLeadingCommentRanges(sourceFileText, closeBrace.getFullStart()) != null; const isSkipped = this.ignoredBlocks.indexOf(node) !== -1; + const shouldIgnoreCatch = this.hasOption(OPTION_IGNORE_CATCH); if (node.statements.length <= 0 && !hasCommentAfter && !hasCommentBefore && !isSkipped) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + if (node.parent.kind === ts.SyntaxKind.CatchClause && shouldIgnoreCatch) { + super.visitBlock(node); + } else { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + } } super.visitBlock(node); diff --git a/test/rules/no-empty/test.js.lint b/test/rules/no-empty/default/test.js.lint similarity index 81% rename from test/rules/no-empty/test.js.lint rename to test/rules/no-empty/default/test.js.lint index 0bbc38c017a..22a45bd0ea4 100644 --- a/test/rules/no-empty/test.js.lint +++ b/test/rules/no-empty/default/test.js.lint @@ -40,3 +40,17 @@ class testClass3 { } ~~~~~ [block is empty] } + +try { + ~ +} catch (e) { +~ [block is empty] + console.log("foo"); +} + +try { + console.log("foo"); +} catch (e) { + ~ +} +~ [block is empty] diff --git a/test/rules/no-empty/default/test.ts.lint b/test/rules/no-empty/default/test.ts.lint new file mode 100644 index 00000000000..99fb9641d6b --- /dev/null +++ b/test/rules/no-empty/default/test.ts.lint @@ -0,0 +1,61 @@ +if (x === 1) {} + ~~ [block is empty] +if (x === 2) { + ~ + +~nil + +~nil +} +~ [block is empty] + +function testFunction() { + ~ + +~nil +} +~ [block is empty] + +for (var x = 0; x < 1; ++x) { } + ~~~ [block is empty] + +// empty blocks with comments should be legal +for (var y = 0; y < 1; ++y) { + // empty here +} + +class testClass { + constructor(private allowed: any, private alsoAllowed: any) { + } +} + +class testClass2 { + constructor(protected allowed: any) { + } +} + +class testClass3 { + constructor(notAllowed: any) { + ~ + } +~~~~~ [block is empty] +} + +class testClass4 { + constructor(readonly allowed: any) { + } +} + +try { + ~ +} catch (e) { +~ [block is empty] + console.log("foo"); +} + +try { + console.log("foo"); +} catch (e) { + ~ +} +~ [block is empty] diff --git a/test/rules/no-empty/tslint.json b/test/rules/no-empty/default/tslint.json similarity index 100% rename from test/rules/no-empty/tslint.json rename to test/rules/no-empty/default/tslint.json diff --git a/test/rules/no-empty/ignore-empty-catch/test.js.lint b/test/rules/no-empty/ignore-empty-catch/test.js.lint new file mode 100644 index 00000000000..706c3a99f8c --- /dev/null +++ b/test/rules/no-empty/ignore-empty-catch/test.js.lint @@ -0,0 +1,60 @@ +if (x === 1) {} + ~~ [block is empty] +if (x === 2) { + ~ + +~nil + +~nil +} +~ [block is empty] + +function testFunction() { + ~ + +~nil +} +~ [block is empty] + +for (var x = 0; x < 1; ++x) { } + ~~~ [block is empty] + +// empty blocks with comments should be legal +for (var y = 0; y < 1; ++y) { + // empty here +} + +class testClass { + constructor(private allowed: any, private alsoAllowed: any) { + } +} + +class testClass2 { + constructor(protected allowed: any) { + } +} + +class testClass3 { + constructor(notAllowed: any) { + ~ + } +~~~~~ [block is empty] +} + +class testClass4 { + constructor(readonly allowed: any) { + } +} + +try { + ~ +} catch (e) { +~ [block is empty] + console.log("foo"); +} + +try { + console.log("foo"); +} catch (e) { + +} diff --git a/test/rules/no-empty/test.ts.lint b/test/rules/no-empty/ignore-empty-catch/test.ts.lint similarity index 85% rename from test/rules/no-empty/test.ts.lint rename to test/rules/no-empty/ignore-empty-catch/test.ts.lint index 9abc44e9d88..706c3a99f8c 100644 --- a/test/rules/no-empty/test.ts.lint +++ b/test/rules/no-empty/ignore-empty-catch/test.ts.lint @@ -45,3 +45,16 @@ class testClass4 { constructor(readonly allowed: any) { } } + +try { + ~ +} catch (e) { +~ [block is empty] + console.log("foo"); +} + +try { + console.log("foo"); +} catch (e) { + +} diff --git a/test/rules/no-empty/ignore-empty-catch/tslint.json b/test/rules/no-empty/ignore-empty-catch/tslint.json new file mode 100644 index 00000000000..35a5b1a1204 --- /dev/null +++ b/test/rules/no-empty/ignore-empty-catch/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "no-empty": [true, "allow-empty-catch"] + }, + "jsRules": { + "no-empty": [true, "allow-empty-catch"] + } +} From e57e3d9a0088019cf0b85aaef2362a54a58d2616 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 8 Dec 2016 10:28:23 -0500 Subject: [PATCH 29/48] Revert "allow empty catch (#1817)" (#1834) This reverts commit 9b8a837438785d15a11899d716b21a3dfe5eb153. --- src/rules/noEmptyRule.ts | 26 ++------ test/rules/no-empty/default/test.ts.lint | 61 ------------------- .../no-empty/ignore-empty-catch/test.js.lint | 60 ------------------ .../no-empty/ignore-empty-catch/tslint.json | 8 --- .../rules/no-empty/{default => }/test.js.lint | 14 ----- .../{ignore-empty-catch => }/test.ts.lint | 13 ---- test/rules/no-empty/{default => }/tslint.json | 0 7 files changed, 4 insertions(+), 178 deletions(-) delete mode 100644 test/rules/no-empty/default/test.ts.lint delete mode 100644 test/rules/no-empty/ignore-empty-catch/test.js.lint delete mode 100644 test/rules/no-empty/ignore-empty-catch/tslint.json rename test/rules/no-empty/{default => }/test.js.lint (81%) rename test/rules/no-empty/{ignore-empty-catch => }/test.ts.lint (85%) rename test/rules/no-empty/{default => }/tslint.json (100%) diff --git a/src/rules/noEmptyRule.ts b/src/rules/noEmptyRule.ts index f9eaebfd337..d6dbbe1b695 100644 --- a/src/rules/noEmptyRule.ts +++ b/src/rules/noEmptyRule.ts @@ -19,8 +19,6 @@ import * as ts from "typescript"; import * as Lint from "../index"; -const OPTION_IGNORE_CATCH = "allow-empty-catch"; - export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { @@ -28,20 +26,9 @@ export class Rule extends Lint.Rules.AbstractRule { description: "Disallows empty blocks.", descriptionDetails: "Blocks with a comment inside are not considered empty.", rationale: "Empty blocks are often indicators of missing code.", - optionsDescription: Lint.Utils.dedent` - One argument may be optionally provided: - - * \`${OPTION_IGNORE_CATCH}\` allows empty catch clause.`, - options: { - type: "array", - items: { - type: "string", - enum: [OPTION_IGNORE_CATCH], - }, - minLength: 0, - maxLength: 1, - }, - optionExamples: ["true", `[true, "${OPTION_IGNORE_CATCH}"]`], + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], type: "functionality", typescriptOnly: false, }; @@ -64,14 +51,9 @@ class BlockWalker extends Lint.RuleWalker { const hasCommentAfter = ts.getTrailingCommentRanges(sourceFileText, openBrace.getEnd()) != null; const hasCommentBefore = ts.getLeadingCommentRanges(sourceFileText, closeBrace.getFullStart()) != null; const isSkipped = this.ignoredBlocks.indexOf(node) !== -1; - const shouldIgnoreCatch = this.hasOption(OPTION_IGNORE_CATCH); if (node.statements.length <= 0 && !hasCommentAfter && !hasCommentBefore && !isSkipped) { - if (node.parent.kind === ts.SyntaxKind.CatchClause && shouldIgnoreCatch) { - super.visitBlock(node); - } else { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); - } + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); } super.visitBlock(node); diff --git a/test/rules/no-empty/default/test.ts.lint b/test/rules/no-empty/default/test.ts.lint deleted file mode 100644 index 99fb9641d6b..00000000000 --- a/test/rules/no-empty/default/test.ts.lint +++ /dev/null @@ -1,61 +0,0 @@ -if (x === 1) {} - ~~ [block is empty] -if (x === 2) { - ~ - -~nil - -~nil -} -~ [block is empty] - -function testFunction() { - ~ - -~nil -} -~ [block is empty] - -for (var x = 0; x < 1; ++x) { } - ~~~ [block is empty] - -// empty blocks with comments should be legal -for (var y = 0; y < 1; ++y) { - // empty here -} - -class testClass { - constructor(private allowed: any, private alsoAllowed: any) { - } -} - -class testClass2 { - constructor(protected allowed: any) { - } -} - -class testClass3 { - constructor(notAllowed: any) { - ~ - } -~~~~~ [block is empty] -} - -class testClass4 { - constructor(readonly allowed: any) { - } -} - -try { - ~ -} catch (e) { -~ [block is empty] - console.log("foo"); -} - -try { - console.log("foo"); -} catch (e) { - ~ -} -~ [block is empty] diff --git a/test/rules/no-empty/ignore-empty-catch/test.js.lint b/test/rules/no-empty/ignore-empty-catch/test.js.lint deleted file mode 100644 index 706c3a99f8c..00000000000 --- a/test/rules/no-empty/ignore-empty-catch/test.js.lint +++ /dev/null @@ -1,60 +0,0 @@ -if (x === 1) {} - ~~ [block is empty] -if (x === 2) { - ~ - -~nil - -~nil -} -~ [block is empty] - -function testFunction() { - ~ - -~nil -} -~ [block is empty] - -for (var x = 0; x < 1; ++x) { } - ~~~ [block is empty] - -// empty blocks with comments should be legal -for (var y = 0; y < 1; ++y) { - // empty here -} - -class testClass { - constructor(private allowed: any, private alsoAllowed: any) { - } -} - -class testClass2 { - constructor(protected allowed: any) { - } -} - -class testClass3 { - constructor(notAllowed: any) { - ~ - } -~~~~~ [block is empty] -} - -class testClass4 { - constructor(readonly allowed: any) { - } -} - -try { - ~ -} catch (e) { -~ [block is empty] - console.log("foo"); -} - -try { - console.log("foo"); -} catch (e) { - -} diff --git a/test/rules/no-empty/ignore-empty-catch/tslint.json b/test/rules/no-empty/ignore-empty-catch/tslint.json deleted file mode 100644 index 35a5b1a1204..00000000000 --- a/test/rules/no-empty/ignore-empty-catch/tslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rules": { - "no-empty": [true, "allow-empty-catch"] - }, - "jsRules": { - "no-empty": [true, "allow-empty-catch"] - } -} diff --git a/test/rules/no-empty/default/test.js.lint b/test/rules/no-empty/test.js.lint similarity index 81% rename from test/rules/no-empty/default/test.js.lint rename to test/rules/no-empty/test.js.lint index 22a45bd0ea4..0bbc38c017a 100644 --- a/test/rules/no-empty/default/test.js.lint +++ b/test/rules/no-empty/test.js.lint @@ -40,17 +40,3 @@ class testClass3 { } ~~~~~ [block is empty] } - -try { - ~ -} catch (e) { -~ [block is empty] - console.log("foo"); -} - -try { - console.log("foo"); -} catch (e) { - ~ -} -~ [block is empty] diff --git a/test/rules/no-empty/ignore-empty-catch/test.ts.lint b/test/rules/no-empty/test.ts.lint similarity index 85% rename from test/rules/no-empty/ignore-empty-catch/test.ts.lint rename to test/rules/no-empty/test.ts.lint index 706c3a99f8c..9abc44e9d88 100644 --- a/test/rules/no-empty/ignore-empty-catch/test.ts.lint +++ b/test/rules/no-empty/test.ts.lint @@ -45,16 +45,3 @@ class testClass4 { constructor(readonly allowed: any) { } } - -try { - ~ -} catch (e) { -~ [block is empty] - console.log("foo"); -} - -try { - console.log("foo"); -} catch (e) { - -} diff --git a/test/rules/no-empty/default/tslint.json b/test/rules/no-empty/tslint.json similarity index 100% rename from test/rules/no-empty/default/tslint.json rename to test/rules/no-empty/tslint.json From 47212df6d3e97eb1e2da6ed0d1fb43cda4e32126 Mon Sep 17 00:00:00 2001 From: Subhash Sharma Date: Thu, 8 Dec 2016 20:59:18 +0530 Subject: [PATCH 30/48] strict boolean expressions (#1820) This rule helps in enforcing usage of boolean expressions in if statements and other constructs where boolean expressions are used also restricts the && and || operators to work with boolean types so that the operator behaves consistently without the need for type coercion. --- src/rules/strictBooleanExpressionsRule.ts | 151 +++++++++++++++ .../strict-boolean-expressions/test.ts.lint | 179 ++++++++++++++++++ .../strict-boolean-expressions/tslint.json | 8 + 3 files changed, 338 insertions(+) create mode 100644 src/rules/strictBooleanExpressionsRule.ts create mode 100644 test/rules/strict-boolean-expressions/test.ts.lint create mode 100644 test/rules/strict-boolean-expressions/tslint.json diff --git a/src/rules/strictBooleanExpressionsRule.ts b/src/rules/strictBooleanExpressionsRule.ts new file mode 100644 index 00000000000..720de93227e --- /dev/null +++ b/src/rules/strictBooleanExpressionsRule.ts @@ -0,0 +1,151 @@ +/** + * @license + * Copyright 2016 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 * as Lint from "../index"; +import * as utils from "../language/utils"; + +export class Rule extends Lint.Rules.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "strict-boolean-expressions", + description: `Usage of && or || operators should be with boolean operands and +expressions in If, Do, While and For statements should be of type boolean`, + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "functionality", + typescriptOnly: true, + requiresTypeInfo: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static BINARY_EXPRESSION_ERROR = "Operands for the && or || operator should be of type boolean"; + public static UNARY_EXPRESSION_ERROR = "Operand for the ! operator should be of type boolean"; + public static STATEMENT_ERROR = "statement condition needs to be a boolean expression or literal"; + public static CONDITIONAL_EXPRESSION_ERROR = "Condition needs to be a boolean expression or literal"; + + public applyWithProgram(srcFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { + return this.applyWithWalker(new StrictBooleanExpressionsRule(srcFile, this.getOptions(), langSvc.getProgram())); + } +} + +type StatementType = ts.IfStatement|ts.DoStatement|ts.WhileStatement; + +class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { + private checker: ts.TypeChecker; + + constructor(srcFile: ts.SourceFile, lintOptions: Lint.IOptions, program: ts.Program) { + super(srcFile, lintOptions, program); + this.checker = this.getTypeChecker(); + } + + public visitBinaryExpression(node: ts.BinaryExpression) { + let isAndAndBinaryOperator = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken; + let isOrOrBinaryOperator = node.operatorToken.kind === ts.SyntaxKind.BarBarToken; + if (isAndAndBinaryOperator || isOrOrBinaryOperator) { + let lhsExpression = node.left; + let lhsType = this.checker.getTypeAtLocation(lhsExpression); + let rhsExpression = node.right; + let rhsType = this.checker.getTypeAtLocation(rhsExpression); + if (!this.isBooleanType(lhsType)) { + if (lhsExpression.kind !== ts.SyntaxKind.BinaryExpression) { + this.addFailure(this.createFailure(lhsExpression.getStart(), lhsExpression.getWidth(), Rule.BINARY_EXPRESSION_ERROR)); + } else { + this.visitBinaryExpression( lhsExpression); + } + } + if (!this.isBooleanType(rhsType)) { + if (rhsExpression.kind !== ts.SyntaxKind.BinaryExpression) { + this.addFailure(this.createFailure(rhsExpression.getStart(), rhsExpression.getWidth(), Rule.BINARY_EXPRESSION_ERROR)); + } else { + this.visitBinaryExpression( rhsExpression); + } + } + } + super.visitBinaryExpression(node); + } + + public visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression) { + let isExclamationOperator = node.operator === ts.SyntaxKind.ExclamationToken; + if (isExclamationOperator) { + let expr = node.operand; + let expType = this.checker.getTypeAtLocation(expr); + if (!this.isBooleanType(expType)) { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.UNARY_EXPRESSION_ERROR)); + } + } + super.visitPrefixUnaryExpression(node); + } + + public visitIfStatement(node: ts.IfStatement) { + this.checkStatement(node); + super.visitIfStatement(node); + } + + public visitWhileStatement(node: ts.WhileStatement) { + this.checkStatement(node); + super.visitWhileStatement(node); + } + + public visitDoStatement(node: ts.DoStatement) { + this.checkStatement(node); + super.visitDoStatement(node); + } + + public visitConditionalExpression(node: ts.ConditionalExpression) { + let cexp = node.condition; + let expType = this.checker.getTypeAtLocation(cexp); + if (!this.isBooleanType(expType)) { + this.addFailure(this.createFailure(cexp.getStart(), cexp.getWidth(), Rule.CONDITIONAL_EXPRESSION_ERROR)); + } + super.visitConditionalExpression(node); + } + + public visitForStatement(node: ts.ForStatement) { + let cexp = node.condition; + let expType = this.checker.getTypeAtLocation(cexp); + if (!this.isBooleanType(expType)) { + this.addFailure(this.createFailure(cexp.getStart(), cexp.getWidth(), `For ${Rule.STATEMENT_ERROR}`)); + } + super.visitForStatement(node); + } + + private checkStatement(node: StatementType) { + let bexp = node.expression; + let expType = this.checker.getTypeAtLocation(bexp); + if (!this.isBooleanType(expType)) { + switch (node.kind) { + case ts.SyntaxKind.IfStatement: + this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `If ${Rule.STATEMENT_ERROR}`)); + break; + case ts.SyntaxKind.DoStatement: + this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `Do-While ${Rule.STATEMENT_ERROR}`)); + break; + case ts.SyntaxKind.WhileStatement: + this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `While ${Rule.STATEMENT_ERROR}`)); + break; + default: + throw new Error("Unknown Syntax Kind"); + } + } + } + + private isBooleanType(btype: ts.Type): boolean { + return utils.isTypeFlagSet(btype, ts.TypeFlags.BooleanLike); + } +} diff --git a/test/rules/strict-boolean-expressions/test.ts.lint b/test/rules/strict-boolean-expressions/test.ts.lint new file mode 100644 index 00000000000..0a308367281 --- /dev/null +++ b/test/rules/strict-boolean-expressions/test.ts.lint @@ -0,0 +1,179 @@ +class C { } +enum MyEnum { + A, B, C +} +let anyType: {}; +let boolType: boolean; +let bwrapType: Boolean; +let numType = 9; +let strType = "string"; +let objType = new Object(); +let classType = new C(); +let enumType = MyEnum.A; +let boolFn = () => { return true; }; +let strFn = () => { return strType; }; +let numFn = () => { return numType; }; +let boolExpr = (strType !== undefined); + +/*** Binary Expressions ***/ +/*** Invalid Boolean Expressions ***/ +classType && boolType; +~~~~~~~~~ [Operands for the && or || operator should be of type boolean] +anyType && boolType; +~~~~~~~ [Operands for the && or || operator should be of type boolean] +numType && boolType; +~~~~~~~ [Operands for the && or || operator should be of type boolean] +boolType && strType; + ~~~~~~~ [Operands for the && or || operator should be of type boolean] +boolType && objType && enumType; + ~~~~~~~ [Operands for the && or || operator should be of type boolean] + ~~~~~~~~ [Operands for the && or || operator should be of type boolean] +bwrapType && boolType; +~~~~~~~~~ [Operands for the && or || operator should be of type boolean] + +boolType || classType; + ~~~~~~~~~ [Operands for the && or || operator should be of type boolean] +boolType || anyType; + ~~~~~~~ [Operands for the && or || operator should be of type boolean] +boolType || numType; + ~~~~~~~ [Operands for the && or || operator should be of type boolean] +strType || boolType; +~~~~~~~ [Operands for the && or || operator should be of type boolean] +bwrapType || boolType; +~~~~~~~~~ [Operands for the && or || operator should be of type boolean] +objType || boolType || enumType; +~~~~~~~ [Operands for the && or || operator should be of type boolean] + ~~~~~~~~ [Operands for the && or || operator should be of type boolean] + +boolExpr && strType; + ~~~~~~~ [Operands for the && or || operator should be of type boolean] +numType || boolExpr; +~~~~~~~ [Operands for the && or || operator should be of type boolean] +numType && boolExpr || strType; +~~~~~~~ [Operands for the && or || operator should be of type boolean] + ~~~~~~~ [Operands for the && or || operator should be of type boolean] +bwrapType || boolExpr && bwrapType; +~~~~~~~~~ [Operands for the && or || operator should be of type boolean] + ~~~~~~~~~ [Operands for the && or || operator should be of type boolean] + +/*** Valid Boolean Expressions ***/ +boolType && boolType; +boolExpr || boolType; +(numType > 0) && boolFn(); +(strType !== "bool") && boolExpr; +(numType > 0) && (strType !== "bool"); +(strType !== undefined) || (numType < 0); + +/*** ConditionalExpression ***/ +/*** Invalid ***/ +strType ? strType : numType; +~~~~~~~ [Condition needs to be a boolean expression or literal] +numType ? numType : numFn(); +~~~~~~~ [Condition needs to be a boolean expression or literal] +objType ? objType : boolExpr; +~~~~~~~ [Condition needs to be a boolean expression or literal] +classType ? strType : undefined; +~~~~~~~~~ [Condition needs to be a boolean expression or literal] +bwrapType ? 1 : 0; +~~~~~~~~~ [Condition needs to be a boolean expression or literal] +enumType ? 0 : 1; +~~~~~~~~ [Condition needs to be a boolean expression or literal] + +/*** Valid ***/ +boolFn() ? numType : strType; +boolType ? strType : undefined; + +/*** PrefixUnary Expressions ***/ +/*** Invalid ***/ +!!numType; + ~~~~~~~~ [Operand for the ! operator should be of type boolean] +!strType; +~~~~~~~~ [Operand for the ! operator should be of type boolean] +!objType; +~~~~~~~~ [Operand for the ! operator should be of type boolean] +!enumType; +~~~~~~~~~ [Operand for the ! operator should be of type boolean] +!!classType; + ~~~~~~~~~~ [Operand for the ! operator should be of type boolean] +!bwrapType; +~~~~~~~~~~ [Operand for the ! operator should be of type boolean] +!!undefined; + ~~~~~~~~~~ [Operand for the ! operator should be of type boolean] + +/*** Valid ***/ +!!boolFn(); +!boolExpr; +!!boolType; + +/*** If Statement ***/ +/*** Invalid ***/ +if (numType) { /* statements */ } + ~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (objType) { /* statements */ } + ~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (strType) { /* statements */ } + ~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (bwrapType) { /* statements */ } + ~~~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (strFn()) { /* statements */ } + ~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (MyEnum.A) { /* statements */ } + ~~~~~~~~ [If statement condition needs to be a boolean expression or literal] +if (classType) { /* statements */ } + ~~~~~~~~~ [If statement condition needs to be a boolean expression or literal] + +/*** Valid ***/ +if (boolFn()) { /* statements */ } +if (boolExpr) { /* statements */ } +if (boolType) { /* statements */ } + +/*** While Statement ***/ +/*** Invalid ***/ +while (numType) { /* statements */ } + ~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (objType) { /* statements */ } + ~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (strType) { /* statements */ } + ~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (strFn()) { /* statements */ } + ~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (bwrapType) { /* statements */ } + ~~~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (MyEnum.A) { /* statements */ } + ~~~~~~~~ [While statement condition needs to be a boolean expression or literal] +while (classType) { /* statements */ } + ~~~~~~~~~ [While statement condition needs to be a boolean expression or literal] + +/*** Valid ***/ +while (boolFn()) { /* statements */ } +while (boolExpr) { /* statements */ } +while (boolType) { /* statements */ } + +/*** Do Statement ***/ +/*** Invalid ***/ +do { /* statements */ } while (numType); + ~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (objType); + ~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (strType); + ~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (bwrapType); + ~~~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (strFn()); + ~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (MyEnum.A); + ~~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] +do { /* statements */ } while (classType); + ~~~~~~~~~ [Do-While statement condition needs to be a boolean expression or literal] + +/*** Valid ***/ +do { /* statements */ } while (boolFn()); +do { /* statements */ } while (boolExpr); +do { /* statements */ } while (boolType); + +/*** For Statement ***/ +/*** Invalid ***/ +for (let j = 0; j; j++) { /* statements */ } + ~ [For statement condition needs to be a boolean expression or literal] +/*** Valid ***/ +for (let j = 0; j > numType; j++) { /* statements */ } \ No newline at end of file diff --git a/test/rules/strict-boolean-expressions/tslint.json b/test/rules/strict-boolean-expressions/tslint.json new file mode 100644 index 00000000000..1060078bc17 --- /dev/null +++ b/test/rules/strict-boolean-expressions/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "strict-boolean-expressions": true + } +} From 8e6a0ce7875ff6dfa2c57476b6817ded4a045e82 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Thu, 8 Dec 2016 21:47:10 +0100 Subject: [PATCH 31/48] Added no-magic-numbers rule (#1799) --- docs/_data/rules.json | 19 ++++ docs/rules/no-magic-numbers/index.html | 32 ++++++ docs/rules/promise-function-async/index.html | 21 ++++ src/rules/noMagicNumbersRule.ts | 106 ++++++++++++++++++ .../no-magic-numbers/custom/test.js.lint | 7 ++ .../no-magic-numbers/custom/test.ts.lint | 7 ++ .../rules/no-magic-numbers/custom/tslint.json | 8 ++ .../no-magic-numbers/default/test.js.lint | 28 +++++ .../no-magic-numbers/default/test.ts.lint | 31 +++++ .../no-magic-numbers/default/tslint.json | 8 ++ 10 files changed, 267 insertions(+) create mode 100644 docs/rules/no-magic-numbers/index.html create mode 100644 docs/rules/promise-function-async/index.html create mode 100644 src/rules/noMagicNumbersRule.ts create mode 100644 test/rules/no-magic-numbers/custom/test.js.lint create mode 100644 test/rules/no-magic-numbers/custom/test.ts.lint create mode 100644 test/rules/no-magic-numbers/custom/tslint.json create mode 100644 test/rules/no-magic-numbers/default/test.js.lint create mode 100644 test/rules/no-magic-numbers/default/test.ts.lint create mode 100644 test/rules/no-magic-numbers/default/tslint.json diff --git a/docs/_data/rules.json b/docs/_data/rules.json index e45072f078f..8452940bdb6 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -677,6 +677,25 @@ "type": "functionality", "typescriptOnly": false }, + { + "ruleName": "no-magic-numbers", + "description": "\nDisallows the use constant number values outside of variable assignments.\nWhen no list of allowed values is specified, -1, 0 and 1 are allowed by default.", + "rationale": "\nMagic numbers should be avoided as they often lack documentation, forcing\nthem to be stored in variables gives them implicit documentation.", + "optionsDescription": "A list of allowed numbers.", + "options": { + "type": "array", + "items": { + "type": "number" + }, + "minLength": 1 + }, + "optionExamples": [ + "true", + "[true, 1, 2, 3]" + ], + "type": "typescript", + "typescriptOnly": false + }, { "ruleName": "no-mergeable-namespace", "description": "Disallows mergeable namespaces in the same file.", diff --git a/docs/rules/no-magic-numbers/index.html b/docs/rules/no-magic-numbers/index.html new file mode 100644 index 00000000000..3ebe8bc3ed0 --- /dev/null +++ b/docs/rules/no-magic-numbers/index.html @@ -0,0 +1,32 @@ +--- +ruleName: no-magic-numbers +description: |- + + Disallows the use constant number values outside of variable assignments. + When no list of allowed values is specified, -1, 0 and 1 are allowed by default. +rationale: |- + + Magic numbers should be avoided as they often lack documentation, forcing + them to be stored in variables gives them implicit documentation. +optionsDescription: A list of allowed numbers. +options: + type: array + items: + type: number + minLength: 1 +optionExamples: + - 'true' + - '[true, 1, 2, 3]' +type: typescript +typescriptOnly: false +layout: rule +title: 'Rule: no-magic-numbers' +optionsJSON: |- + { + "type": "array", + "items": { + "type": "number" + }, + "minLength": 1 + } +--- \ No newline at end of file diff --git a/docs/rules/promise-function-async/index.html b/docs/rules/promise-function-async/index.html new file mode 100644 index 00000000000..2da06dd6f4a --- /dev/null +++ b/docs/rules/promise-function-async/index.html @@ -0,0 +1,21 @@ +--- +ruleName: promise-function-async +description: Requires any function or method that returns a promise to be marked async. +rationale: |- + + Ensures that each function is only capable of 1) returning a rejected promise, or 2) + throwing an Error object. In contrast, non-`async` `Promise`-returning functions + are technically capable of either. This practice removes a requirement for consuming + code to handle both cases. + +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: typescript +typescriptOnly: false +requiresTypeInfo: true +layout: rule +title: 'Rule: promise-function-async' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/src/rules/noMagicNumbersRule.ts b/src/rules/noMagicNumbersRule.ts new file mode 100644 index 00000000000..f84fa611cd1 --- /dev/null +++ b/src/rules/noMagicNumbersRule.ts @@ -0,0 +1,106 @@ +/** + * @license + * Copyright 2016 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 * as Lint from "../index"; + +import { IOptions } from "../language/rule/rule"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "no-magic-numbers", + description: Lint.Utils.dedent` + Disallows the use constant number values outside of variable assignments. + When no list of allowed values is specified, -1, 0 and 1 are allowed by default.`, + rationale: Lint.Utils.dedent` + Magic numbers should be avoided as they often lack documentation, forcing + them to be stored in variables gives them implicit documentation.`, + optionsDescription: "A list of allowed numbers.", + options: { + type: "array", + items: { + type: "number", + }, + minLength: 1, + }, + optionExamples: ["true", "[true, 1, 2, 3]"], + type: "typescript", + typescriptOnly: false, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "'magic numbers' are not allowed"; + + public static ALLOWED_NODES = { + [ts.SyntaxKind.ExportAssignment]: true, + [ts.SyntaxKind.FirstAssignment]: true, + [ts.SyntaxKind.LastAssignment]: true, + [ts.SyntaxKind.PropertyAssignment]: true, + [ts.SyntaxKind.ShorthandPropertyAssignment]: true, + [ts.SyntaxKind.VariableDeclaration]: true, + [ts.SyntaxKind.VariableDeclarationList]: true, + [ts.SyntaxKind.EnumMember]: true, + [ts.SyntaxKind.PropertyDeclaration]: true, + }; + + public static DEFAULT_ALLOWED = [ -1, 0, 1 ]; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new NoMagicNumbersWalker(sourceFile, this.getOptions())); + } +} + +class NoMagicNumbersWalker extends Lint.RuleWalker { + // lookup object for allowed magic numbers + private allowed: { [prop: string]: boolean } = {}; + constructor (sourceFile: ts.SourceFile, options: IOptions) { + super(sourceFile, options); + + const configOptions = this.getOptions(); + const allowedNumbers: number[] = configOptions.length > 0 ? configOptions : Rule.DEFAULT_ALLOWED; + + allowedNumbers.forEach((value) => { + this.allowed[value] = true; + }); + } + + public visitNode(node: ts.Node) { + const isUnary = this.isUnaryNumericExpression(node); + if (node.kind === ts.SyntaxKind.NumericLiteral && !Rule.ALLOWED_NODES[node.parent.kind] || isUnary) { + let text = node.getText(); + if (!this.allowed[text]) { + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + } + } + if (!isUnary) { + super.visitNode(node); + } + } + + /** + * Checks if a node is an unary expression with on a numeric operand. + */ + private isUnaryNumericExpression(node: ts.Node): boolean { + if (node.kind !== ts.SyntaxKind.PrefixUnaryExpression) { + return false; + } + const unaryNode = ( node); + return unaryNode.operator === ts.SyntaxKind.MinusToken && unaryNode.operand.kind === ts.SyntaxKind.NumericLiteral; + } +} diff --git a/test/rules/no-magic-numbers/custom/test.js.lint b/test/rules/no-magic-numbers/custom/test.js.lint new file mode 100644 index 00000000000..32b2173416e --- /dev/null +++ b/test/rules/no-magic-numbers/custom/test.js.lint @@ -0,0 +1,7 @@ +console.log(1337); +console.log(-1337); +console.log(1337.7); +console.log(1338); + ~~~~ ['magic numbers' are not allowed] +console.log(-1338) + ~~~~~ ['magic numbers' are not allowed] diff --git a/test/rules/no-magic-numbers/custom/test.ts.lint b/test/rules/no-magic-numbers/custom/test.ts.lint new file mode 100644 index 00000000000..32b2173416e --- /dev/null +++ b/test/rules/no-magic-numbers/custom/test.ts.lint @@ -0,0 +1,7 @@ +console.log(1337); +console.log(-1337); +console.log(1337.7); +console.log(1338); + ~~~~ ['magic numbers' are not allowed] +console.log(-1338) + ~~~~~ ['magic numbers' are not allowed] diff --git a/test/rules/no-magic-numbers/custom/tslint.json b/test/rules/no-magic-numbers/custom/tslint.json new file mode 100644 index 00000000000..cfb9496660c --- /dev/null +++ b/test/rules/no-magic-numbers/custom/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "no-magic-numbers": [true, 1337, 1337.7, -1337] + }, + "jsRules": { + "no-magic-numbers": [true, 1337, 1337.7, -1337] + } +} diff --git a/test/rules/no-magic-numbers/default/test.js.lint b/test/rules/no-magic-numbers/default/test.js.lint new file mode 100644 index 00000000000..c7874cdf571 --- /dev/null +++ b/test/rules/no-magic-numbers/default/test.js.lint @@ -0,0 +1,28 @@ +console.log(-1, 0, 1); +console.log(42.42); + ~~~~~ ['magic numbers' are not allowed] + +const a = 1337; +const b = { + a: 1338, +} +b.b = 1339; + ~~~~ ['magic numbers' are not allowed] + +const c = 42.42; + +console.log(1340); + ~~~~ ['magic numbers' are not allowed] +for(let i = 0;i>1341;++i) { + ~~~~ ['magic numbers' are not allowed] +} + +throw 1342; + ~~~~ ['magic numbers' are not allowed] + +class A { + constructor () { + this.a = 1345; + ~~~~ ['magic numbers' are not allowed] + } +} diff --git a/test/rules/no-magic-numbers/default/test.ts.lint b/test/rules/no-magic-numbers/default/test.ts.lint new file mode 100644 index 00000000000..ec6214e7159 --- /dev/null +++ b/test/rules/no-magic-numbers/default/test.ts.lint @@ -0,0 +1,31 @@ +console.log(-1, 0, 1); +console.log(42.42); + ~~~~~ ['magic numbers' are not allowed] +const a = 1337; +const b = { + a: 1338, + b: 0, +} +b.b = 1339; + ~~~~ ['magic numbers' are not allowed] + +console.log(1340); + ~~~~ ['magic numbers' are not allowed] +for(let i = 0;i>1341;++i) { + ~~~~ ['magic numbers' are not allowed] +} + +throw 1342; + ~~~~ ['magic numbers' are not allowed] + +class A { + static test = 1337; + + constructor (private a = 1337) { + ~~~~ ['magic numbers' are not allowed] + } +} + +enum Test { + key = 1337, +} diff --git a/test/rules/no-magic-numbers/default/tslint.json b/test/rules/no-magic-numbers/default/tslint.json new file mode 100644 index 00000000000..07ee5c0a857 --- /dev/null +++ b/test/rules/no-magic-numbers/default/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "no-magic-numbers": true + }, + "jsRules": { + "no-magic-numbers": true + } +} From 63644325d3e717d32c719d8ba0907ff245bf7669 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Thu, 8 Dec 2016 13:04:58 -0800 Subject: [PATCH 32/48] Use @types/babel-code-frame to import babel-code-frame (#1835) --- package.json | 1 + src/formatters/codeFrameFormatter.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 8c94cda38a0..4b00bda4faa 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "update-notifier": "^1.0.2" }, "devDependencies": { + "@types/babel-code-frame": "^6.16.0", "@types/chai": "^3.4.34", "@types/colors": "^0.6.33", "@types/diff": "0.0.31", diff --git a/src/formatters/codeFrameFormatter.ts b/src/formatters/codeFrameFormatter.ts index 088718caf8d..5807763a4b8 100644 --- a/src/formatters/codeFrameFormatter.ts +++ b/src/formatters/codeFrameFormatter.ts @@ -19,11 +19,8 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; import {RuleFailure} from "../language/rule/rule"; +import * as codeFrame from "babel-code-frame"; import * as colors from "colors"; -// TODO: once types for babel-code-frame are published, add it as a dependency and use import -// @types/babel-code-frame PR: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/13102 -// tslint:disable-next-line:no-var-requires -const codeFrame = require("babel-code-frame"); import * as Utils from "../utils"; From b2f385e7b1f6b128e6f6222128fac5af5b589cf3 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 12 Dec 2016 06:28:59 -0800 Subject: [PATCH 33/48] Fix import of babel-code-frame module (#1846) --- package.json | 2 +- src/formatters/codeFrameFormatter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4b00bda4faa..66414a8d558 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "verify": "npm-run-all clean compile lint test docs" }, "dependencies": { - "babel-code-frame": "^6.16.0", + "babel-code-frame": "^6.20.0", "colors": "^1.1.2", "diff": "^3.0.1", "findup-sync": "~0.3.0", diff --git a/src/formatters/codeFrameFormatter.ts b/src/formatters/codeFrameFormatter.ts index 5807763a4b8..8dcef9ca40c 100644 --- a/src/formatters/codeFrameFormatter.ts +++ b/src/formatters/codeFrameFormatter.ts @@ -19,7 +19,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; import {RuleFailure} from "../language/rule/rule"; -import * as codeFrame from "babel-code-frame"; +import codeFrame = require("babel-code-frame"); import * as colors from "colors"; import * as Utils from "../utils"; From d0533553fed521fd252a951f8dbdebe337e19c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Sypie=C5=84?= Date: Mon, 12 Dec 2016 15:30:58 +0100 Subject: [PATCH 34/48] Add missing line for proper docs generation (#1842) --- src/rules/orderedImportsRule.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rules/orderedImportsRule.ts b/src/rules/orderedImportsRule.ts index a78e4fc26e2..8b28251131d 100644 --- a/src/rules/orderedImportsRule.ts +++ b/src/rules/orderedImportsRule.ts @@ -39,6 +39,7 @@ export class Rule extends Lint.Rules.AbstractRule { imports (the \`"foo"\` in \`import {A, B, C} from "foo"\`). Possible values for \`"import-sources-order"\` are: + * \`"case-insensitive'\`: Correct order is \`"Bar"\`, \`"baz"\`, \`"Foo"\`. (This is the default.) * \`"lowercase-first"\`: Correct order is \`"baz"\`, \`"Bar"\`, \`"Foo"\`. * \`"lowercase-last"\`: Correct order is \`"Bar"\`, \`"Foo"\`, \`"baz"\`. From edeff3b3a8ee691b904e7b94866ec1789579425d Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Mon, 12 Dec 2016 22:22:32 -0500 Subject: [PATCH 35/48] Upgrade to TypeScript v2.1.4 (#1836) - Retain compatibility with v2.0.x --- package.json | 2 +- src/language/utils.ts | 16 ++++++++-- src/linter.ts | 3 +- src/rules/arrowParensRule.ts | 3 +- src/rules/noConditionalAssignmentRule.ts | 2 +- src/rules/noInferredEmptyObjectTypeRule.ts | 16 ++++++++-- src/rules/noNamespaceRule.ts | 2 +- src/rules/preferConstRule.ts | 3 +- src/rules/restrictPlusOperandsRule.ts | 29 ++++++++++-------- src/rules/typedefRule.ts | 2 +- src/test.ts | 1 + .../rules/restrict-plus-operands/test.js.lint | 30 +++++++++---------- .../rules/restrict-plus-operands/test.ts.lint | 30 +++++++++---------- 13 files changed, 84 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 66414a8d558..99805a88408 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "rimraf": "^2.5.4", "tslint": "latest", "tslint-test-config-non-relative": "file:test/external/tslint-test-config-non-relative", - "typescript": "2.0.10" + "typescript": "2.1.4" }, "peerDependencies": { "typescript": ">=2.0.0" diff --git a/src/language/utils.ts b/src/language/utils.ts index 3bcdbb40ce9..bdcb8bc3518 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -77,7 +77,7 @@ export function scanAllTokens(scanner: ts.Scanner, callback: (s: ts.Scanner) => /** * @returns true if any modifier kinds passed along exist in the given modifiers array */ -export function hasModifier(modifiers: ts.ModifiersArray|undefined, ...modifierKinds: ts.SyntaxKind[]) { +export function hasModifier(modifiers: ts.ModifiersArray | undefined, ...modifierKinds: ts.SyntaxKind[]) { if (modifiers === undefined || modifierKinds.length === 0) { return false; } @@ -147,9 +147,19 @@ export function isNodeFlagSet(node: ts.Node, flagToCheck: ts.NodeFlags): boolean /** * Bitwise check for type flags. */ -export function isTypeFlagSet(typ: ts.Type, flagToCheck: ts.TypeFlags): boolean { +export function isTypeFlagSet(type: ts.Type, flagToCheck: ts.TypeFlags): boolean { /* tslint:disable:no-bitwise */ - return (typ.flags & flagToCheck) !== 0; + return (type.flags & flagToCheck) !== 0; + /* tslint:enable:no-bitwise */ +} + +/** + * Bitwise check for object flags. + * Does not work with TypeScript 2.0.x + */ +export function isObjectFlagSet(objectType: ts.ObjectType, flagToCheck: ts.ObjectFlags): boolean { + /* tslint:disable:no-bitwise */ + return (objectType.objectFlags & flagToCheck) !== 0; /* tslint:enable:no-bitwise */ } diff --git a/src/linter.ts b/src/linter.ts index f83357f85df..45d576bdbe2 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -67,9 +67,10 @@ class Linter { } const { config } = ts.readConfigFile(configFile, ts.sys.readFile); - const parseConfigHost = { + const parseConfigHost: ts.ParseConfigHost = { fileExists: fs.existsSync, readDirectory: ts.sys.readDirectory, + readFile: (file) => fs.readFileSync(file, "utf8"), useCaseSensitiveFileNames: true, }; const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, projectDirectory); diff --git a/src/rules/arrowParensRule.ts b/src/rules/arrowParensRule.ts index ea877d5d9a1..dbfbd354ca0 100644 --- a/src/rules/arrowParensRule.ts +++ b/src/rules/arrowParensRule.ts @@ -18,6 +18,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; +import { hasModifier } from "../language/utils"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -58,7 +59,7 @@ class ArrowParensWalker extends Lint.RuleWalker { } if ((firstToken.kind !== ts.SyntaxKind.OpenParenToken || lastToken.kind !== ts.SyntaxKind.CloseParenToken) - && !isGenerics && node.flags !== ts.NodeFlags.Async) { + && !isGenerics && !hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)) { const fix = new Lint.Fix(Rule.metadata.ruleName, [new Lint.Replacement(position, width, `(${parameter.getText()})`)]); this.addFailure(this.createFailure(position, width, Rule.FAILURE_STRING, fix)); diff --git a/src/rules/noConditionalAssignmentRule.ts b/src/rules/noConditionalAssignmentRule.ts index 89ad7ff898e..74817de5c35 100644 --- a/src/rules/noConditionalAssignmentRule.ts +++ b/src/rules/noConditionalAssignmentRule.ts @@ -60,7 +60,7 @@ class NoConditionalAssignmentWalker extends Lint.RuleWalker { protected visitDoStatement(node: ts.DoStatement) { this.validateConditionalExpression(node.expression); - super.visitWhileStatement(node); + super.visitDoStatement(node); } protected visitForStatement(node: ts.ForStatement) { diff --git a/src/rules/noInferredEmptyObjectTypeRule.ts b/src/rules/noInferredEmptyObjectTypeRule.ts index 5c9b392ff73..b8a1bf923df 100644 --- a/src/rules/noInferredEmptyObjectTypeRule.ts +++ b/src/rules/noInferredEmptyObjectTypeRule.ts @@ -51,7 +51,13 @@ class NoInferredEmptyObjectTypeRule extends Lint.ProgramAwareRuleWalker { public visitNewExpression(node: ts.NewExpression): void { let nodeTypeArgs = node.typeArguments; - let isObjectReference = (o: ts.TypeReference) => utils.isTypeFlagSet(o, ts.TypeFlags.Reference); + let isObjectReference: (o: ts.TypeReference) => boolean; + if ((ts as any).TypeFlags.Reference != null) { + // typescript 2.0.x specific code + isObjectReference = (o: ts.TypeReference) => utils.isTypeFlagSet(o, (ts as any).TypeFlags.Reference); + } else { + isObjectReference = (o: ts.TypeReference) => utils.isTypeFlagSet(o, ts.TypeFlags.Object); + } if (nodeTypeArgs === undefined) { let objType = this.checker.getTypeAtLocation(node) as ts.TypeReference; if (isObjectReference(objType) && objType.typeArguments !== undefined) { @@ -78,7 +84,13 @@ class NoInferredEmptyObjectTypeRule extends Lint.ProgramAwareRuleWalker { } private isEmptyObjectInterface(objType: ts.ObjectType): boolean { - let isAnonymous = utils.isTypeFlagSet(objType, ts.TypeFlags.Anonymous); + let isAnonymous: boolean; + if (ts.ObjectFlags == null) { + // typescript 2.0.x specific code + isAnonymous = utils.isTypeFlagSet(objType, (ts as any).TypeFlags.Anonymous); + } else { + isAnonymous = utils.isObjectFlagSet(objType, ts.ObjectFlags.Anonymous); + } let hasProblematicCallSignatures = false; let hasProperties = (objType.getProperties() !== undefined && objType.getProperties().length > 0); let hasNumberIndexType = objType.getNumberIndexType() !== undefined; diff --git a/src/rules/noNamespaceRule.ts b/src/rules/noNamespaceRule.ts index 0113eb1d8dd..7625969889b 100644 --- a/src/rules/noNamespaceRule.ts +++ b/src/rules/noNamespaceRule.ts @@ -72,7 +72,7 @@ class NoNamespaceWalker extends Lint.RuleWalker { return; } if (this.hasOption("allow-declarations") - && Lint.someAncestor(decl, (n) => Lint.isNodeFlagSet(n, ts.NodeFlags.Ambient))) { + && Lint.someAncestor(decl, (n) => Lint.hasModifier(n.modifiers, ts.SyntaxKind.DeclareKeyword))) { return; } if (Lint.isNestedModuleDeclaration(decl)) { diff --git a/src/rules/preferConstRule.ts b/src/rules/preferConstRule.ts index 7276054ea99..af28aa8d87e 100644 --- a/src/rules/preferConstRule.ts +++ b/src/rules/preferConstRule.ts @@ -98,7 +98,8 @@ class PreferConstWalker extends Lint.BlockScopeAwareRuleWalker<{}, ScopeInfo> { protected visitIdentifier(node: ts.Identifier) { if (this.getCurrentBlockScope().currentVariableDeclaration != null) { const declarationList = this.getCurrentBlockScope().currentVariableDeclaration.parent; - if (isNodeFlagSet(declarationList, ts.NodeFlags.Let) && !isNodeFlagSet(declarationList.parent, ts.NodeFlags.Export)) { + if (isNodeFlagSet(declarationList, ts.NodeFlags.Let) + && !Lint.hasModifier(declarationList.parent.modifiers, ts.SyntaxKind.ExportKeyword)) { if (this.isVariableDeclaration(node)) { this.getCurrentBlockScope().addVariable(node, declarationList); } diff --git a/src/rules/restrictPlusOperandsRule.ts b/src/rules/restrictPlusOperandsRule.ts index ec16c412894..ea4542ccc06 100644 --- a/src/rules/restrictPlusOperandsRule.ts +++ b/src/rules/restrictPlusOperandsRule.ts @@ -33,10 +33,7 @@ export class Rule extends Lint.Rules.TypedRule { }; /* tslint:enable:object-literal-sort-keys */ - public static MISMATCHED_TYPES_FAILURE = "Types of values used in '+' operation must match"; - public static UNSUPPORTED_TYPE_FAILURE_FACTORY = (type: string) => { - return `cannot add type ${type}`; - } + public static INVALID_TYPES_ERROR = "Operands of '+' operation must either be both strings or both numbers"; public applyWithProgram(sourceFile: ts.SourceFile, langSvc: ts.LanguageService): Lint.RuleFailure[] { return this.applyWithWalker(new RestrictPlusOperandsWalker(sourceFile, this.getOptions(), langSvc.getProgram())); @@ -47,22 +44,28 @@ class RestrictPlusOperandsWalker extends Lint.ProgramAwareRuleWalker { public visitBinaryExpression(node: ts.BinaryExpression) { if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) { const tc = this.getTypeChecker(); - const leftType = tc.typeToString(tc.getTypeAtLocation(node.left)); - const rightType = tc.typeToString(tc.getTypeAtLocation(node.right)); + + const leftType = getAddableType(node.left, tc); + const rightType = getAddableType(node.right, tc); const width = node.getWidth(); const position = node.getStart(); - if (leftType !== rightType) { - // mismatched types - this.addFailure(this.createFailure(position, width, Rule.MISMATCHED_TYPES_FAILURE)); - } else if (leftType !== "number" && leftType !== "string") { - // adding unsupported types - const failureString = Rule.UNSUPPORTED_TYPE_FAILURE_FACTORY(leftType); - this.addFailure(this.createFailure(position, width, failureString)); + if (leftType === "invalid" || rightType === "invalid" || leftType !== rightType) { + this.addFailure(this.createFailure(position, width, Rule.INVALID_TYPES_ERROR)); } } super.visitBinaryExpression(node); } } + +function getAddableType(node: ts.Expression, tc: ts.TypeChecker): "string" | "number" | "invalid" { + const type = tc.getTypeAtLocation(node); + if (type.flags === ts.TypeFlags.NumberLiteral || type.flags === ts.TypeFlags.Number) { + return "number"; + } else if (type.flags === ts.TypeFlags.StringLiteral || type.flags === ts.TypeFlags.String) { + return "string"; + } + return "invalid"; +} diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index 7e0f5b7dce0..fb8ab0f20ab 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -185,7 +185,7 @@ class TypedefWalker extends Lint.RuleWalker { // catch statements will be the parent of the variable declaration // for-in/for-of loops will be the gradparent of the variable declaration if (node.parent != null && node.parent.parent != null - && node.parent.kind !== ts.SyntaxKind.CatchClause + && node.parent.parent.kind !== ts.SyntaxKind.CatchClause && node.parent.parent.kind !== ts.SyntaxKind.ForInStatement && node.parent.parent.kind !== ts.SyntaxKind.ForOfStatement) { this.checkTypeAnnotation("variable-declaration", node.name.getEnd(), node.type, node.name); diff --git a/src/test.ts b/src/test.ts index 84fcc53ada5..10b5462a97d 100644 --- a/src/test.ts +++ b/src/test.ts @@ -58,6 +58,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ const parseConfigHost = { fileExists: fs.existsSync, readDirectory: ts.sys.readDirectory, + readFile: (file: string) => fs.readFileSync(file, "utf8"), useCaseSensitiveFileNames: true, }; compilerOptions = ts.parseJsonConfigFileContent(config, parseConfigHost, testDirectory).options; diff --git a/test/rules/restrict-plus-operands/test.js.lint b/test/rules/restrict-plus-operands/test.js.lint index e5e92360603..8de32839ca1 100644 --- a/test/rules/restrict-plus-operands/test.js.lint +++ b/test/rules/restrict-plus-operands/test.js.lint @@ -10,35 +10,35 @@ var pair = { // bad var bad1 = 5 + "10"; - ~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad2 = [] + 5; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad3 = [] + {}; - ~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad4 = [] + []; - ~~~~~~~ [cannot add type undefined[]] + ~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad4 = 5 + []; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad5 = "5" + {}; - ~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad6 = 5.5 + "5"; - ~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad7 = "5.5" + 5; - ~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad8 = x + y; - ~~~~~ [Types of values used in '+' operation must match] + ~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad9 = y + x; - ~~~~~ [Types of values used in '+' operation must match] + ~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad10 = x + {}; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad11 = [] + y; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad12 = pair.first + "10"; - ~~~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad13 = 5 + pair.second; - ~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad14 = pair + pair; - ~~~~~~~~~~~ [cannot add type { first: number; second: string; }] + ~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] // good var good1 = 5 + 10; diff --git a/test/rules/restrict-plus-operands/test.ts.lint b/test/rules/restrict-plus-operands/test.ts.lint index b289818c685..25ef0ef66b7 100644 --- a/test/rules/restrict-plus-operands/test.ts.lint +++ b/test/rules/restrict-plus-operands/test.ts.lint @@ -17,35 +17,35 @@ var pair: NumberStringPair = { // bad var bad1 = 5 + "10"; - ~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad2 = [] + 5; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad3 = [] + {}; - ~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad4 = [] + []; - ~~~~~~~ [cannot add type undefined[]] + ~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad4 = 5 + []; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad5 = "5" + {}; - ~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad6 = 5.5 + "5"; - ~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad7 = "5.5" + 5; - ~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad8 = x + y; - ~~~~~ [Types of values used in '+' operation must match] + ~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad9 = y + x; - ~~~~~ [Types of values used in '+' operation must match] + ~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad10 = x + {}; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad11 = [] + y; - ~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad12 = pair.first + "10"; - ~~~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad13 = 5 + pair.second; - ~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] + ~~~~~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] var bad14 = pair + pair; - ~~~~~~~~~~~ [cannot add type NumberStringPair] + ~~~~~~~~~~~ [Operands of '+' operation must either be both strings or both numbers] // good var good1 = 5 + 10; From 4a33a4b8c174dbe53aa894f88c5e79d77c06419c Mon Sep 17 00:00:00 2001 From: Donald Pipowitch Date: Tue, 13 Dec 2016 04:24:16 +0100 Subject: [PATCH 36/48] Update Node API usage documentation (#1838) Fixes #1837 --- docs/usage/library/index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/usage/library/index.md b/docs/usage/library/index.md index ef758bb60fb..4fce3452473 100644 --- a/docs/usage/library/index.md +++ b/docs/usage/library/index.md @@ -32,15 +32,16 @@ var configuration = { var options = { formatter: "json", - configuration: configuration, rulesDirectory: "customRules/", // can be an array of directories - formattersDirectory: "customFormatters/" + formattersDirectory: "customFormatters/", + fix: false }; -var Linter = require("tslint"); +var Linter = require("tslint").Linter; var fs = require("fs"); var contents = fs.readFileSync(fileName, "utf8"); -var ll = new Linter(fileName, contents, options); -var result = ll.lint(); +var linter = new Linter(options); +linter.lint(fileName, contents, configuration); +var result = linter.getResult(); ``` From 6979cdaec50a0bc8e82db90b55052a52238e9b7d Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 13 Dec 2016 04:26:17 +0100 Subject: [PATCH 37/48] New rule: import-blacklist (#1841) This rule checks for require/import statements with a user-specified list of banned strings. This is useful to prevent accidental inclusions of entire libraries, when tree shaking is not possible. E.g. importing "material-ui" instead of "material-ui/lib/something". Contributors: @use-strict --- docs/_data/rules.json | 43 +++++++ docs/rules/import-blacklist/index.html | 32 ++++++ .../no-inferred-empty-object-type/index.html | 14 +++ docs/rules/prefer-const/index.html | 16 +++ .../strict-boolean-expressions/index.html | 16 +++ package.json | 2 +- src/formatters/codeFrameFormatter.ts | 2 +- src/rules/importBlacklistRule.ts | 108 ++++++++++++++++++ test/rules/import-blacklist/test.js.lint | 7 ++ test/rules/import-blacklist/test.ts.lint | 9 ++ test/rules/import-blacklist/tslint.json | 8 ++ 11 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 docs/rules/import-blacklist/index.html create mode 100644 docs/rules/no-inferred-empty-object-type/index.html create mode 100644 docs/rules/prefer-const/index.html create mode 100644 docs/rules/strict-boolean-expressions/index.html create mode 100644 src/rules/importBlacklistRule.ts create mode 100644 test/rules/import-blacklist/test.js.lint create mode 100644 test/rules/import-blacklist/test.ts.lint create mode 100644 test/rules/import-blacklist/tslint.json diff --git a/docs/_data/rules.json b/docs/_data/rules.json index 8452940bdb6..e6ae7e1e2c1 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -213,6 +213,25 @@ "type": "functionality", "typescriptOnly": false }, + { + "ruleName": "import-blacklist", + "description": "\nDisallows importing the specified modules directly via `import` and `require`.\nInstead only sub modules may be imported from that module.", + "rationale": "\nSome libraries allow importing their submodules instead of the entire module.\nThis is good practise as it avoids loading unused modules.", + "optionsDescription": "A list of blacklisted modules.", + "options": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1 + }, + "optionExamples": [ + "true", + "[true, \"rxjs\", \"lodash\"]" + ], + "type": "functionality", + "typescriptOnly": false + }, { "ruleName": "indent", "description": "Enforces indentation with tabs or spaces.", @@ -642,6 +661,18 @@ "type": "typescript", "typescriptOnly": true }, + { + "ruleName": "no-inferred-empty-object-type", + "description": "Disallow type inference of {} (empty object type) at function and constructor call sites", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "functionality", + "typescriptOnly": true, + "requiresTypeInfo": true + }, { "ruleName": "no-internal-module", "description": "Disallows internal `module`", @@ -1209,6 +1240,18 @@ "type": "style", "typescriptOnly": false }, + { + "ruleName": "strict-boolean-expressions", + "description": "Usage of && or || operators should be with boolean operands and\nexpressions in If, Do, While and For statements should be of type boolean", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "functionality", + "typescriptOnly": true, + "requiresTypeInfo": true + }, { "ruleName": "switch-default", "description": "Require a `default` case in all `switch` statements.", diff --git a/docs/rules/import-blacklist/index.html b/docs/rules/import-blacklist/index.html new file mode 100644 index 00000000000..41ec9639819 --- /dev/null +++ b/docs/rules/import-blacklist/index.html @@ -0,0 +1,32 @@ +--- +ruleName: import-blacklist +description: |- + + Disallows importing the specified modules directly via `import` and `require`. + Instead only sub modules may be imported from that module. +rationale: |- + + Some libraries allow importing their submodules instead of the entire module. + This is good practise as it avoids loading unused modules. +optionsDescription: A list of blacklisted modules. +options: + type: array + items: + type: string + minLength: 1 +optionExamples: + - 'true' + - '[true, "rxjs", "lodash"]' +type: functionality +typescriptOnly: false +layout: rule +title: 'Rule: import-blacklist' +optionsJSON: |- + { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1 + } +--- \ No newline at end of file diff --git a/docs/rules/no-inferred-empty-object-type/index.html b/docs/rules/no-inferred-empty-object-type/index.html new file mode 100644 index 00000000000..aa2a9c3f558 --- /dev/null +++ b/docs/rules/no-inferred-empty-object-type/index.html @@ -0,0 +1,14 @@ +--- +ruleName: no-inferred-empty-object-type +description: 'Disallow type inference of {} (empty object type) at function and constructor call sites' +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: functionality +typescriptOnly: true +requiresTypeInfo: true +layout: rule +title: 'Rule: no-inferred-empty-object-type' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/docs/rules/prefer-const/index.html b/docs/rules/prefer-const/index.html new file mode 100644 index 00000000000..f6900e093f5 --- /dev/null +++ b/docs/rules/prefer-const/index.html @@ -0,0 +1,16 @@ +--- +ruleName: prefer-const +description: Requires that variable declarations use `const` instead of `let` if possible. +descriptionDetails: |- + + If a variable is only assigned to once when it is declared, it should be declared using 'const' +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: prefer-const' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/docs/rules/strict-boolean-expressions/index.html b/docs/rules/strict-boolean-expressions/index.html new file mode 100644 index 00000000000..a3bf5cbd61c --- /dev/null +++ b/docs/rules/strict-boolean-expressions/index.html @@ -0,0 +1,16 @@ +--- +ruleName: strict-boolean-expressions +description: |- + Usage of && or || operators should be with boolean operands and + expressions in If, Do, While and For statements should be of type boolean +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: functionality +typescriptOnly: true +requiresTypeInfo: true +layout: rule +title: 'Rule: strict-boolean-expressions' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/package.json b/package.json index 99805a88408..047ac1dd9c2 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "update-notifier": "^1.0.2" }, "devDependencies": { - "@types/babel-code-frame": "^6.16.0", + "@types/babel-code-frame": "^6.20.0", "@types/chai": "^3.4.34", "@types/colors": "^0.6.33", "@types/diff": "0.0.31", diff --git a/src/formatters/codeFrameFormatter.ts b/src/formatters/codeFrameFormatter.ts index 8dcef9ca40c..fe83140ee77 100644 --- a/src/formatters/codeFrameFormatter.ts +++ b/src/formatters/codeFrameFormatter.ts @@ -17,7 +17,7 @@ import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; -import {RuleFailure} from "../language/rule/rule"; +import { RuleFailure } from "../language/rule/rule"; import codeFrame = require("babel-code-frame"); import * as colors from "colors"; diff --git a/src/rules/importBlacklistRule.ts b/src/rules/importBlacklistRule.ts new file mode 100644 index 00000000000..79cd9473b99 --- /dev/null +++ b/src/rules/importBlacklistRule.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2016 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 * as Lint from "../index"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "import-blacklist", + description: Lint.Utils.dedent` + Disallows importing the specified modules directly via \`import\` and \`require\`. + Instead only sub modules may be imported from that module.`, + rationale: Lint.Utils.dedent` + Some libraries allow importing their submodules instead of the entire module. + This is good practise as it avoids loading unused modules.`, + optionsDescription: "A list of blacklisted modules.", + options: { + type: "array", + items: { + type: "string", + }, + minLength: 1, + }, + optionExamples: ["true", '[true, "rxjs", "lodash"]'], + type: "functionality", + typescriptOnly: false, + }; + + public static FAILURE_STRING = "This import is blacklisted, import a submodule instead"; + + public isEnabled(): boolean { + return super.isEnabled() && this.getOptions().ruleArguments.length > 0; + } + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + const options = this.getOptions(); + return this.applyWithWalker( + new NoRequireFullLibraryWalker(sourceFile, options, options.ruleArguments), + ); + } +} + +class NoRequireFullLibraryWalker extends Lint.RuleWalker { + private blacklist: string[]; + constructor (sourceFile: ts.SourceFile, options: Lint.IOptions, blacklist: string[]) { + super(sourceFile, options); + this.blacklist = blacklist; + } + + public visitCallExpression(node: ts.CallExpression) { + if ( + node.expression.getText() === "require" && + node.arguments && + node.arguments[0] && + this.isModuleBlacklisted(node.arguments[0].getText()) + ) { + this.reportFailure(node.arguments[0]); + } + super.visitCallExpression(node); + } + + public visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration) { + let moduleReference = node.moduleReference; + // If it's an import require and not an import alias + if (moduleReference.expression) { + if (this.isModuleBlacklisted(moduleReference.expression.getText())) { + this.reportFailure(moduleReference.expression); + } + } + super.visitImportEqualsDeclaration(node); + } + + public visitImportDeclaration(node: ts.ImportDeclaration) { + if (this.isModuleBlacklisted(node.moduleSpecifier.getText())) { + this.reportFailure(node.moduleSpecifier); + } + super.visitImportDeclaration(node); + } + + private isModuleBlacklisted(text: string): boolean { + return this.blacklist.some((entry) => { + return text.substring(1, text.length - 1) === entry; + }); + } + + private reportFailure (node: ts.Expression): void { + this.addFailure(this.createFailure( + node.getStart() + 1, // take quotes into account + node.getWidth() - 2, + Rule.FAILURE_STRING, + )); + } +} diff --git a/test/rules/import-blacklist/test.js.lint b/test/rules/import-blacklist/test.js.lint new file mode 100644 index 00000000000..2c155eb4bb3 --- /dev/null +++ b/test/rules/import-blacklist/test.js.lint @@ -0,0 +1,7 @@ +const _ = require('lodash'); + ~~~~~~ [0] +const forOwn = require('lodash').forOwn; + ~~~~~~ [0] +const forOwn = require('lodash/forOwn'); + +[0]: This import is blacklisted, import a submodule instead diff --git a/test/rules/import-blacklist/test.ts.lint b/test/rules/import-blacklist/test.ts.lint new file mode 100644 index 00000000000..25bac38c85c --- /dev/null +++ b/test/rules/import-blacklist/test.ts.lint @@ -0,0 +1,9 @@ +import { Observable } from 'rxjs'; + ~~~~ [0] +import { Observable } from 'rxjs/Observable'; + +import forOwn = require('lodash'); + ~~~~~~ [0] +import forOwn = require('lodash/forOwn'); + +[0]: This import is blacklisted, import a submodule instead diff --git a/test/rules/import-blacklist/tslint.json b/test/rules/import-blacklist/tslint.json new file mode 100644 index 00000000000..4d0c035d514 --- /dev/null +++ b/test/rules/import-blacklist/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "import-blacklist": [true, "lodash", "rxjs"] + }, + "jsRules": { + "import-blacklist": [true, "lodash", "rxjs"] + } +} From 1ef6c071c39b09a4055826c7e372cc3a9b40dac4 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 12 Dec 2016 19:42:38 -0800 Subject: [PATCH 38/48] Add "allow-named-functions" option for only-arrow-functions (#1857) --- src/rules/onlyArrowFunctionsRule.ts | 12 ++++++++---- .../allow-named-functions/test.js.lint | 5 +++++ .../allow-named-functions/test.ts.lint | 5 +++++ .../allow-named-functions/tslint.json | 8 ++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 test/rules/only-arrow-functions/allow-named-functions/test.js.lint create mode 100644 test/rules/only-arrow-functions/allow-named-functions/test.ts.lint create mode 100644 test/rules/only-arrow-functions/allow-named-functions/tslint.json diff --git a/src/rules/onlyArrowFunctionsRule.ts b/src/rules/onlyArrowFunctionsRule.ts index ad7734e071b..b04fb132121 100644 --- a/src/rules/onlyArrowFunctionsRule.ts +++ b/src/rules/onlyArrowFunctionsRule.ts @@ -20,6 +20,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; const OPTION_ALLOW_DECLARATIONS = "allow-declarations"; +const OPTION_ALLOW_NAMED_FUNCTIONS = "allow-named-functions"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -28,20 +29,21 @@ export class Rule extends Lint.Rules.AbstractRule { description: "Disallows traditional (non-arrow) function expressions.", rationale: "Traditional functions don't bind lexical scope, which can lead to unexpected behavior when accessing 'this'.", optionsDescription: Lint.Utils.dedent` - One argument may be optionally provided: + Two arguments may be optionally provided: * \`"${OPTION_ALLOW_DECLARATIONS}"\` allows standalone function declarations. + * \`"${OPTION_ALLOW_NAMED_FUNCTIONS}"\` allows the expression \`function foo() {}\` but not \`function() {}\`. `, options: { type: "array", items: { type: "string", - enum: [OPTION_ALLOW_DECLARATIONS], + enum: [OPTION_ALLOW_DECLARATIONS, OPTION_ALLOW_NAMED_FUNCTIONS], }, minLength: 0, maxLength: 1, }, - optionExamples: ["true", `[true, "${OPTION_ALLOW_DECLARATIONS}"]`], + optionExamples: ["true", `[true, "${OPTION_ALLOW_DECLARATIONS}", "${OPTION_ALLOW_NAMED_FUNCTIONS}"]`], type: "typescript", typescriptOnly: false, }; @@ -63,7 +65,9 @@ class OnlyArrowFunctionsWalker extends Lint.RuleWalker { } public visitFunctionExpression(node: ts.FunctionExpression) { - this.failUnlessExempt(node); + if (!(node.name && this.hasOption(OPTION_ALLOW_NAMED_FUNCTIONS))) { + this.failUnlessExempt(node); + } super.visitFunctionExpression(node); } diff --git a/test/rules/only-arrow-functions/allow-named-functions/test.js.lint b/test/rules/only-arrow-functions/allow-named-functions/test.js.lint new file mode 100644 index 00000000000..97a281561d1 --- /dev/null +++ b/test/rules/only-arrow-functions/allow-named-functions/test.js.lint @@ -0,0 +1,5 @@ +const x = function() {} + ~~~~~~~~ [0] +const y = function y() {} + +[0]: non-arrow functions are forbidden diff --git a/test/rules/only-arrow-functions/allow-named-functions/test.ts.lint b/test/rules/only-arrow-functions/allow-named-functions/test.ts.lint new file mode 100644 index 00000000000..97a281561d1 --- /dev/null +++ b/test/rules/only-arrow-functions/allow-named-functions/test.ts.lint @@ -0,0 +1,5 @@ +const x = function() {} + ~~~~~~~~ [0] +const y = function y() {} + +[0]: non-arrow functions are forbidden diff --git a/test/rules/only-arrow-functions/allow-named-functions/tslint.json b/test/rules/only-arrow-functions/allow-named-functions/tslint.json new file mode 100644 index 00000000000..ae7af8ce845 --- /dev/null +++ b/test/rules/only-arrow-functions/allow-named-functions/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "only-arrow-functions": [true, "allow-named-functions"] + }, + "jsRules": { + "only-arrow-functions": [true, "allow-named-functions"] + } +} From e63d0db49b1a42f5518434f5acf683be1489b91b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 13 Dec 2016 18:14:51 -0800 Subject: [PATCH 39/48] Simplify code for adding failures (#1845) --- src/language/walker/ruleWalker.ts | 15 ++++++++ src/rules/adjacentOverloadSignaturesRule.ts | 3 +- src/rules/alignRule.ts | 2 +- src/rules/arrayTypeRule.ts | 6 ++-- src/rules/arrowParensRule.ts | 2 +- src/rules/banRule.ts | 15 +++----- src/rules/classNameRule.ts | 9 ++--- src/rules/commentFormatRule.ts | 9 ++--- src/rules/completedDocsRule.ts | 6 ++-- src/rules/curlyRule.ts | 26 ++++---------- src/rules/cyclomaticComplexityRule.ts | 2 +- src/rules/fileHeaderRule.ts | 4 +-- src/rules/forinRule.ts | 3 +- src/rules/importBlacklistRule.ts | 4 +-- src/rules/indentRule.ts | 2 +- src/rules/interfaceNameRule.ts | 10 ++---- src/rules/jsdocFormatRule.ts | 5 --- src/rules/labelPositionRule.ts | 3 +- src/rules/maxClassesPerFileRule.ts | 2 +- src/rules/memberAccessRule.ts | 2 +- src/rules/memberOrderingRule.ts | 14 ++------ src/rules/newParensRule.ts | 2 +- src/rules/noAngleBracketTypeAssertionRule.ts | 2 +- src/rules/noAnyRule.ts | 2 +- src/rules/noArgRule.ts | 2 +- src/rules/noBitwiseRule.ts | 4 +-- src/rules/noConditionalAssignmentRule.ts | 2 +- src/rules/noConsecutiveBlankLinesRule.ts | 2 +- src/rules/noConstructRule.ts | 3 +- src/rules/noDebuggerRule.ts | 2 +- src/rules/noDefaultExportRule.ts | 4 +-- src/rules/noDuplicateVariableRule.ts | 7 +--- src/rules/noEmptyRule.ts | 2 +- src/rules/noEvalRule.ts | 2 +- src/rules/noForInArrayRule.ts | 2 +- src/rules/noInferrableTypesRule.ts | 2 +- src/rules/noInferredEmptyObjectTypeRule.ts | 4 +-- src/rules/noInternalModuleRule.ts | 2 +- src/rules/noInvalidThisRule.ts | 4 +-- src/rules/noMagicNumbersRule.ts | 2 +- src/rules/noMergeableNamespaceRule.ts | 2 +- src/rules/noNamespaceRule.ts | 2 +- src/rules/noNullKeywordRule.ts | 2 +- src/rules/noParameterPropertiesRule.ts | 3 +- src/rules/noReferenceRule.ts | 2 +- src/rules/noRequireImportsRule.ts | 4 +-- src/rules/noShadowedVariableRule.ts | 2 +- src/rules/noStringLiteralRule.ts | 2 +- src/rules/noSwitchCaseFallThroughRule.ts | 9 ++--- src/rules/noTrailingWhitespaceRule.ts | 4 +-- src/rules/noUnsafeFinallyRule.ts | 12 +++---- src/rules/noUnusedExpressionRule.ts | 2 +- src/rules/noUnusedNewRule.ts | 2 +- src/rules/noUnusedVariableRule.ts | 2 +- src/rules/noUseBeforeDeclareRule.ts | 2 +- src/rules/noVarKeywordRule.ts | 5 ++- src/rules/noVarRequiresRule.ts | 2 +- src/rules/objectLiteralKeyQuotesRule.ts | 4 +-- src/rules/objectLiteralShorthandRule.ts | 4 +-- src/rules/objectLiteralSortKeysRule.ts | 2 +- src/rules/oneLineRule.ts | 17 ++++----- src/rules/oneVariablePerDeclarationRule.ts | 4 +-- src/rules/onlyArrowFunctionsRule.ts | 2 +- src/rules/orderedImportsRule.ts | 16 +++------ src/rules/preferConstRule.ts | 7 +--- src/rules/preferForOfRule.ts | 4 +-- src/rules/promiseFunctionAsyncRule.ts | 2 +- src/rules/quotemarkRule.ts | 2 +- src/rules/radixRule.ts | 4 +-- src/rules/restrictPlusOperandsRule.ts | 2 +- src/rules/semicolonRule.ts | 4 +-- src/rules/strictBooleanExpressionsRule.ts | 37 ++++++++++---------- src/rules/switchDefaultRule.ts | 2 +- src/rules/trailingCommaRule.ts | 4 +-- src/rules/tripleEqualsRule.ts | 4 +-- src/rules/typedefRule.ts | 3 +- src/rules/typedefWhitespaceRule.ts | 2 +- src/rules/useIsnanRule.ts | 2 +- src/rules/variableNameRule.ts | 4 +-- src/rules/whitespaceRule.ts | 5 ++- 80 files changed, 162 insertions(+), 230 deletions(-) diff --git a/src/language/walker/ruleWalker.ts b/src/language/walker/ruleWalker.ts index 7c17dee7602..eab79631cf2 100644 --- a/src/language/walker/ruleWalker.ts +++ b/src/language/walker/ruleWalker.ts @@ -81,6 +81,21 @@ export class RuleWalker extends SyntaxWalker { } } + /** Add a failure with any arbitrary span. Prefer `addFailureAtNode` if possible. */ + public addFailureAt(start: number, width: number, failure: string, fix?: Fix) { + this.addFailure(this.createFailure(start, width, failure, fix)); + } + + /** Like `addFailureAt` but uses start and end instead of start and width. */ + public addFailureFromStartToEnd(start: number, end: number, failure: string, fix?: Fix) { + this.addFailureAt(start, end - start, failure, fix); + } + + /** 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); + } + public createReplacement(start: number, length: number, text: string): Replacement { return new Replacement(start, length, text); } diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index cd4c74fb519..afd9b9e3657 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -99,8 +99,7 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { const name = getOverloadName(node); if (name !== undefined) { if (name in seen && last !== name) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), - Rule.FAILURE_STRING_FACTORY(name))); + this.addFailureAtNode(node, Rule.FAILURE_STRING_FACTORY(name)); } seen[name] = true; } diff --git a/src/rules/alignRule.ts b/src/rules/alignRule.ts index 44bb0aa7256..d2c52e049a5 100644 --- a/src/rules/alignRule.ts +++ b/src/rules/alignRule.ts @@ -111,7 +111,7 @@ class AlignWalker extends Lint.RuleWalker { for (let node of nodes.slice(1)) { const curPos = getPosition(node); if (curPos.line !== prevPos.line && curPos.character !== alignToColumn) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), kind + Rule.FAILURE_STRING_SUFFIX)); + this.addFailureAtNode(node, kind + Rule.FAILURE_STRING_SUFFIX); break; } prevPos = curPos; diff --git a/src/rules/arrayTypeRule.ts b/src/rules/arrayTypeRule.ts index 7b294c9e1d2..cf58e16a69f 100644 --- a/src/rules/arrayTypeRule.ts +++ b/src/rules/arrayTypeRule.ts @@ -52,7 +52,7 @@ class ArrayTypeWalker extends Lint.RuleWalker { // Delete the square brackets and replace with an angle bracket this.createReplacement(typeName.getEnd() - parens, node.getEnd() - typeName.getEnd() + parens, ">"), ]); - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failureString, fix)); + this.addFailureAtNode(node, failureString, fix); } super.visitArrayType(node); @@ -68,7 +68,7 @@ class ArrayTypeWalker extends Lint.RuleWalker { const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.createReplacement(node.getStart(), node.getWidth(), "any[]"), ]); - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failureString, fix)); + this.addFailureAtNode(node, failureString, fix); } else if (typeArgs && typeArgs.length === 1 && (!this.hasOption(OPTION_ARRAY_SIMPLE) || this.isSimpleType(typeArgs[0]))) { const type = typeArgs[0]; const typeStart = type.getStart(); @@ -81,7 +81,7 @@ class ArrayTypeWalker extends Lint.RuleWalker { // Delete the last angle bracket and replace with square brackets this.createReplacement(typeEnd, node.getEnd() - typeEnd, (parens ? ")" : "") + "[]"), ]); - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failureString, fix)); + this.addFailureAtNode(node, failureString, fix); } } diff --git a/src/rules/arrowParensRule.ts b/src/rules/arrowParensRule.ts index dbfbd354ca0..bc89008ee82 100644 --- a/src/rules/arrowParensRule.ts +++ b/src/rules/arrowParensRule.ts @@ -62,7 +62,7 @@ class ArrowParensWalker extends Lint.RuleWalker { && !isGenerics && !hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)) { const fix = new Lint.Fix(Rule.metadata.ruleName, [new Lint.Replacement(position, width, `(${parameter.getText()})`)]); - this.addFailure(this.createFailure(position, width, Rule.FAILURE_STRING, fix)); + this.addFailureAt(position, width, Rule.FAILURE_STRING, fix); } } super.visitArrowFunction(node); diff --git a/src/rules/banRule.ts b/src/rules/banRule.ts index 341f7276026..29fb25cfa59 100644 --- a/src/rules/banRule.ts +++ b/src/rules/banRule.ts @@ -25,7 +25,7 @@ export class Rule extends Lint.Rules.AbstractRule { ruleName: "ban", description: "Bans the use of specific functions or global methods.", optionsDescription: Lint.Utils.dedent` - A list of \`['object', 'method', 'optional explanation here']\` or \`['globalMethod']\` which ban \`object.method()\` + A list of \`['object', 'method', 'optional explanation here']\` or \`['globalMethod']\` which ban \`object.method()\` or respectively \`globalMethod()\`.`, options: { type: "list", @@ -36,7 +36,7 @@ export class Rule extends Lint.Rules.AbstractRule { maxLength: 3, }, }, - optionExamples: [`[true, ["someGlobalMethod"], ["someObject", "someFunction"], + optionExamples: [`[true, ["someGlobalMethod"], ["someObject", "someFunction"], ["someObject", "otherFunction", "Optional explanation"]]`], type: "functionality", typescriptOnly: false, @@ -99,12 +99,8 @@ export class BanFunctionWalker extends Lint.RuleWalker { if (secondChild.kind === ts.SyntaxKind.DotToken) { for (const bannedFunction of this.bannedFunctions) { if (leftSideExpression === bannedFunction[0] && rightSideExpression === bannedFunction[1]) { - const failure = this.createFailure( - expression.getStart(), - expression.getWidth(), - Rule.FAILURE_STRING_FACTORY(`${leftSideExpression}.${rightSideExpression}`, bannedFunction[2]), - ); - this.addFailure(failure); + const failure = Rule.FAILURE_STRING_FACTORY(`${leftSideExpression}.${rightSideExpression}`, bannedFunction[2]); + this.addFailureAtNode(expression, failure); } } } @@ -115,8 +111,7 @@ export class BanFunctionWalker extends Lint.RuleWalker { if (expression.kind === ts.SyntaxKind.Identifier) { const identifierName = ( expression).text; if (this.bannedGlobalFunctions.indexOf(identifierName) !== -1) { - this.addFailure(this.createFailure(expression.getStart(), expression.getWidth(), - Rule.FAILURE_STRING_FACTORY(`${identifierName}`))); + this.addFailureAtNode(expression, Rule.FAILURE_STRING_FACTORY(`${identifierName}`)); } } diff --git a/src/rules/classNameRule.ts b/src/rules/classNameRule.ts index 802fdbf41a0..f934d4d3580 100644 --- a/src/rules/classNameRule.ts +++ b/src/rules/classNameRule.ts @@ -46,7 +46,7 @@ class NameWalker extends Lint.RuleWalker { if (node.name != null) { const className = node.name.getText(); if (!this.isPascalCased(className)) { - this.addFailureAt(node.name.getStart(), node.name.getWidth()); + this.addFailureAtNode(node.name, Rule.FAILURE_STRING); } } @@ -56,7 +56,7 @@ class NameWalker extends Lint.RuleWalker { public visitInterfaceDeclaration(node: ts.InterfaceDeclaration) { const interfaceName = node.name.getText(); if (!this.isPascalCased(interfaceName)) { - this.addFailureAt(node.name.getStart(), node.name.getWidth()); + this.addFailureAtNode(node.name, Rule.FAILURE_STRING); } super.visitInterfaceDeclaration(node); @@ -70,9 +70,4 @@ class NameWalker extends Lint.RuleWalker { const firstCharacter = name.charAt(0); return ((firstCharacter === firstCharacter.toUpperCase()) && name.indexOf("_") === -1); } - - private addFailureAt(position: number, width: number) { - const failure = this.createFailure(position, width, Rule.FAILURE_STRING); - this.addFailure(failure); - } } diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index c51689d56d7..e65a16b3fc3 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -78,20 +78,17 @@ class CommentWalker extends Lint.SkippableTokenAwareRuleWalker { const width = commentText.length - 2; if (this.hasOption(OPTION_SPACE)) { if (!startsWithSpace(commentText)) { - const leadingSpaceFailure = this.createFailure(startPosition, width, Rule.LEADING_SPACE_FAILURE); - this.addFailure(leadingSpaceFailure); + this.addFailureAt(startPosition, width, Rule.LEADING_SPACE_FAILURE); } } if (this.hasOption(OPTION_LOWERCASE)) { if (!startsWithLowercase(commentText)) { - const lowercaseFailure = this.createFailure(startPosition, width, Rule.LOWERCASE_FAILURE); - this.addFailure(lowercaseFailure); + this.addFailureAt(startPosition, width, Rule.LOWERCASE_FAILURE); } } if (this.hasOption(OPTION_UPPERCASE)) { if (!startsWithUppercase(commentText) && !isEnableDisableFlag(commentText)) { - const uppercaseFailure = this.createFailure(startPosition, width, Rule.UPPERCASE_FAILURE); - this.addFailure(uppercaseFailure); + this.addFailureAt(startPosition, width, Rule.UPPERCASE_FAILURE); } } } diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index 13e6c905865..2d32d4ec6a0 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -107,15 +107,15 @@ export class CompletedDocsWalker extends Lint.ProgramAwareRuleWalker { const comments = this.getTypeChecker().getSymbolAtLocation(node.name).getDocumentationComment(); if (comments.map((comment) => comment.text).join("").trim() === "") { - this.addFailure(this.createDocumentationFailure(node, nodeToCheck)); + this.addDocumentationFailure(node, nodeToCheck); } } - private createDocumentationFailure(node: ts.Declaration, nodeToCheck: string): Lint.RuleFailure { + private addDocumentationFailure(node: ts.Declaration, nodeToCheck: string): void { const start = node.getStart(); const width = node.getText().split(/\r|\n/g)[0].length; const description = nodeToCheck[0].toUpperCase() + nodeToCheck.substring(1) + Rule.FAILURE_STRING_EXIST; - return this.createFailure(start, width, description); + this.addFailureAt(start, width, description); } } diff --git a/src/rules/curlyRule.ts b/src/rules/curlyRule.ts index bcddb77b349..998d58c0dde 100644 --- a/src/rules/curlyRule.ts +++ b/src/rules/curlyRule.ts @@ -56,7 +56,7 @@ export class Rule extends Lint.Rules.AbstractRule { class CurlyWalker extends Lint.RuleWalker { public visitForInStatement(node: ts.ForInStatement) { if (!isStatementBraced(node.statement)) { - this.addFailureForNode(node, Rule.FOR_FAILURE_STRING); + this.addFailureAtNode(node, Rule.FOR_FAILURE_STRING); } super.visitForInStatement(node); @@ -64,7 +64,7 @@ class CurlyWalker extends Lint.RuleWalker { public visitForOfStatement(node: ts.ForOfStatement) { if (!isStatementBraced(node.statement)) { - this.addFailureForNode(node, Rule.FOR_FAILURE_STRING); + this.addFailureAtNode(node, Rule.FOR_FAILURE_STRING); } super.visitForOfStatement(node); @@ -72,7 +72,7 @@ class CurlyWalker extends Lint.RuleWalker { public visitForStatement(node: ts.ForStatement) { if (!isStatementBraced(node.statement)) { - this.addFailureForNode(node, Rule.FOR_FAILURE_STRING); + this.addFailureAtNode(node, Rule.FOR_FAILURE_STRING); } super.visitForStatement(node); @@ -80,11 +80,7 @@ class CurlyWalker extends Lint.RuleWalker { public visitIfStatement(node: ts.IfStatement) { if (!isStatementBraced(node.thenStatement)) { - this.addFailure(this.createFailure( - node.getStart(), - node.thenStatement.getEnd() - node.getStart(), - Rule.IF_FAILURE_STRING, - )); + this.addFailureFromStartToEnd(node.getStart(), node.thenStatement.getEnd(), Rule.IF_FAILURE_STRING); } if (node.elseStatement != null @@ -94,11 +90,7 @@ class CurlyWalker extends Lint.RuleWalker { // find the else keyword to place the error appropriately const elseKeywordNode = node.getChildren().filter((child) => child.kind === ts.SyntaxKind.ElseKeyword)[0]; - this.addFailure(this.createFailure( - elseKeywordNode.getStart(), - node.elseStatement.getEnd() - elseKeywordNode.getStart(), - Rule.ELSE_FAILURE_STRING, - )); + this.addFailureFromStartToEnd(elseKeywordNode.getStart(), node.elseStatement.getEnd(), Rule.ELSE_FAILURE_STRING); } super.visitIfStatement(node); @@ -106,7 +98,7 @@ class CurlyWalker extends Lint.RuleWalker { public visitDoStatement(node: ts.DoStatement) { if (!isStatementBraced(node.statement)) { - this.addFailureForNode(node, Rule.DO_FAILURE_STRING); + this.addFailureAtNode(node, Rule.DO_FAILURE_STRING); } super.visitDoStatement(node); @@ -114,15 +106,11 @@ class CurlyWalker extends Lint.RuleWalker { public visitWhileStatement(node: ts.WhileStatement) { if (!isStatementBraced(node.statement)) { - this.addFailureForNode(node, Rule.WHILE_FAILURE_STRING); + this.addFailureAtNode(node, Rule.WHILE_FAILURE_STRING); } super.visitWhileStatement(node); } - - private addFailureForNode(node: ts.Node, failure: string) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failure)); - } } function isStatementBraced(node: ts.Statement) { diff --git a/src/rules/cyclomaticComplexityRule.ts b/src/rules/cyclomaticComplexityRule.ts index ddefdee516f..8cc21f54960 100644 --- a/src/rules/cyclomaticComplexityRule.ts +++ b/src/rules/cyclomaticComplexityRule.ts @@ -203,7 +203,7 @@ class CyclomaticComplexityWalker extends Lint.RuleWalker { failureString = Rule.ANONYMOUS_FAILURE_STRING(this.threshold, complexity); } - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failureString)); + this.addFailureAtNode(node, failureString); } } diff --git a/src/rules/fileHeaderRule.ts b/src/rules/fileHeaderRule.ts index 1434482e00c..a80cd2c3471 100644 --- a/src/rules/fileHeaderRule.ts +++ b/src/rules/fileHeaderRule.ts @@ -49,12 +49,12 @@ class FileHeaderWalker extends Lint.RuleWalker { // check for a comment const match = text.match(this.commentRegexp); if (!match) { - this.addFailure(this.createFailure(offset, 0, Rule.FAILURE_STRING)); + this.addFailureAt(offset, 0, Rule.FAILURE_STRING); } else { // either the third or fourth capture group contains the comment contents const comment = match[2] ? match[2] : match[3]; if (comment.search(this.headerRegexp) < 0) { - this.addFailure(this.createFailure(offset, 0, Rule.FAILURE_STRING)); + this.addFailureAt(offset, 0, Rule.FAILURE_STRING); } } } diff --git a/src/rules/forinRule.ts b/src/rules/forinRule.ts index 2459cabf43d..6a792af12e4 100644 --- a/src/rules/forinRule.ts +++ b/src/rules/forinRule.ts @@ -86,8 +86,7 @@ class ForInWalker extends Lint.RuleWalker { } } - const failure = this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } } diff --git a/src/rules/importBlacklistRule.ts b/src/rules/importBlacklistRule.ts index 79cd9473b99..089998fce6e 100644 --- a/src/rules/importBlacklistRule.ts +++ b/src/rules/importBlacklistRule.ts @@ -99,10 +99,10 @@ class NoRequireFullLibraryWalker extends Lint.RuleWalker { } private reportFailure (node: ts.Expression): void { - this.addFailure(this.createFailure( + this.addFailureAt( node.getStart() + 1, // take quotes into account node.getWidth() - 2, Rule.FAILURE_STRING, - )); + ); } } diff --git a/src/rules/indentRule.ts b/src/rules/indentRule.ts index 5cdb939da04..4784964db98 100644 --- a/src/rules/indentRule.ts +++ b/src/rules/indentRule.ts @@ -136,7 +136,7 @@ class IndentWalker extends Lint.RuleWalker { } if (fullLeadingWhitespace.match(this.regExp)) { - this.addFailure(this.createFailure(lineStart, fullLeadingWhitespace.length, this.failureString)); + this.addFailureAt(lineStart, fullLeadingWhitespace.length, this.failureString); } } // no need to call super to visit the rest of the nodes, so don't call super here diff --git a/src/rules/interfaceNameRule.ts b/src/rules/interfaceNameRule.ts index 5cea37f4c65..8a8fa0025d4 100644 --- a/src/rules/interfaceNameRule.ts +++ b/src/rules/interfaceNameRule.ts @@ -59,11 +59,11 @@ class NameWalker extends Lint.RuleWalker { if (always) { if (!this.startsWithI(interfaceName)) { - this.addFailureAt(node.name.getStart(), node.name.getWidth(), Rule.FAILURE_STRING); + this.addFailureAtNode(node.name, Rule.FAILURE_STRING); } } else if (this.hasOption(OPTION_NEVER)) { if (this.hasPrefixI(interfaceName)) { - this.addFailureAt(node.name.getStart(), node.name.getWidth(), Rule.FAILURE_STRING_NO_PREFIX); + this.addFailureAtNode(node.name, Rule.FAILURE_STRING_NO_PREFIX); } } @@ -103,10 +103,4 @@ class NameWalker extends Lint.RuleWalker { return true; } - - private addFailureAt(position: number, width: number, failureString: string) { - const failure = this.createFailure(position, width, failureString); - this.addFailure(failure); - } - } diff --git a/src/rules/jsdocFormatRule.ts b/src/rules/jsdocFormatRule.ts index 40e8f172804..238c1320d89 100644 --- a/src/rules/jsdocFormatRule.ts +++ b/src/rules/jsdocFormatRule.ts @@ -119,9 +119,4 @@ class JsdocWalker extends Lint.SkippableTokenAwareRuleWalker { } } } - - private addFailureAt(currentPosition: number, width: number, failureString: string) { - const failure = this.createFailure(currentPosition, width, failureString); - this.addFailure(failure); - } } diff --git a/src/rules/labelPositionRule.ts b/src/rules/labelPositionRule.ts index 30650f1af87..564666026a4 100644 --- a/src/rules/labelPositionRule.ts +++ b/src/rules/labelPositionRule.ts @@ -53,8 +53,7 @@ class LabelPositionWalker extends Lint.RuleWalker { && statement.kind !== ts.SyntaxKind.ForOfStatement && statement.kind !== ts.SyntaxKind.WhileStatement && statement.kind !== ts.SyntaxKind.SwitchStatement) { - const failure = this.createFailure(node.label.getStart(), node.label.getWidth(), Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureAtNode(node.label, Rule.FAILURE_STRING); } super.visitLabeledStatement(node); } diff --git a/src/rules/maxClassesPerFileRule.ts b/src/rules/maxClassesPerFileRule.ts index 04b4a933ccc..543a0c14636 100644 --- a/src/rules/maxClassesPerFileRule.ts +++ b/src/rules/maxClassesPerFileRule.ts @@ -67,7 +67,7 @@ class MaxClassesPerFileWalker extends Lint.RuleWalker { this.classCount++; if (this.classCount > this.maxClassCount) { const msg = Rule.FAILURE_STRING_FACTORY(this.maxClassCount); - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), msg)); + this.addFailureAtNode(node, msg); } } } diff --git a/src/rules/memberAccessRule.ts b/src/rules/memberAccessRule.ts index 8fef76d20f3..58a3da6abc3 100644 --- a/src/rules/memberAccessRule.ts +++ b/src/rules/memberAccessRule.ts @@ -130,7 +130,7 @@ export class MemberAccessWalker extends Lint.RuleWalker { }); const failureString = Rule.FAILURE_STRING_FACTORY(memberType, memberName, publicOnly); - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), failureString)); + this.addFailureAtNode(node, failureString); } } } diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index 9b4201f4cce..13f6e5da1cb 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -296,12 +296,8 @@ export class MemberOrderingWalker extends Lint.RuleWalker { private checkModifiersAndSetPrevious(node: ts.Node, currentMember: IModifiers) { if (!this.canAppearAfter(this.previousMember, currentMember)) { - const failure = this.createFailure( - node.getStart(), - node.getWidth(), - `Declaration of ${toString(currentMember)} not allowed to appear after declaration of ${toString(this.previousMember)}`, - ); - this.addFailure(failure); + this.addFailureAtNode(node, + `Declaration of ${toString(currentMember)} not allowed to appear after declaration of ${toString(this.previousMember)}`); } this.previousMember = currentMember; } @@ -365,11 +361,7 @@ export class MemberOrderingWalker extends Lint.RuleWalker { const errorLine1 = `Declaration of ${nodeType} not allowed after declaration of ${prevNodeType}. ` + `Instead, this should come ${locationHint}.`; - this.addFailure(this.createFailure( - node.getStart(), - node.getWidth(), - errorLine1, - )); + this.addFailureAtNode(node, errorLine1); } else { // keep track of last good node prevRank = rank; diff --git a/src/rules/newParensRule.ts b/src/rules/newParensRule.ts index 7feae34434f..bc0182be4da 100644 --- a/src/rules/newParensRule.ts +++ b/src/rules/newParensRule.ts @@ -44,7 +44,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NewParensWalker extends Lint.RuleWalker { public visitNewExpression(node: ts.NewExpression) { if (node.arguments === undefined) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitNewExpression(node); } diff --git a/src/rules/noAngleBracketTypeAssertionRule.ts b/src/rules/noAngleBracketTypeAssertionRule.ts index ebf381c52a3..236dd0fa8d2 100644 --- a/src/rules/noAngleBracketTypeAssertionRule.ts +++ b/src/rules/noAngleBracketTypeAssertionRule.ts @@ -45,7 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NoAngleBracketTypeAssertionWalker extends Lint.RuleWalker { public visitTypeAssertionExpression(node: ts.TypeAssertion) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); super.visitTypeAssertionExpression(node); } } diff --git a/src/rules/noAnyRule.ts b/src/rules/noAnyRule.ts index 7346f151838..0f843334b38 100644 --- a/src/rules/noAnyRule.ts +++ b/src/rules/noAnyRule.ts @@ -42,7 +42,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NoAnyWalker extends Lint.RuleWalker { public visitAnyKeyword(node: ts.Node) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); super.visitAnyKeyword(node); } } diff --git a/src/rules/noArgRule.ts b/src/rules/noArgRule.ts index 4adb5d727db..3e43e73bba3 100644 --- a/src/rules/noArgRule.ts +++ b/src/rules/noArgRule.ts @@ -51,7 +51,7 @@ class NoArgWalker extends Lint.RuleWalker { if (expression.kind === ts.SyntaxKind.Identifier && name.text === "callee") { const identifierExpression = expression as ts.Identifier; if (identifierExpression.text === "arguments") { - this.addFailure(this.createFailure(expression.getStart(), expression.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(expression, Rule.FAILURE_STRING); } } diff --git a/src/rules/noBitwiseRule.ts b/src/rules/noBitwiseRule.ts index bb69045394d..0abcff3df9a 100644 --- a/src/rules/noBitwiseRule.ts +++ b/src/rules/noBitwiseRule.ts @@ -63,7 +63,7 @@ class NoBitwiseWalker extends Lint.RuleWalker { case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); break; default: break; @@ -73,7 +73,7 @@ class NoBitwiseWalker extends Lint.RuleWalker { public visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression) { if (node.operator === ts.SyntaxKind.TildeToken) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitPrefixUnaryExpression(node); } diff --git a/src/rules/noConditionalAssignmentRule.ts b/src/rules/noConditionalAssignmentRule.ts index 74817de5c35..b2791e19abb 100644 --- a/src/rules/noConditionalAssignmentRule.ts +++ b/src/rules/noConditionalAssignmentRule.ts @@ -90,7 +90,7 @@ class NoConditionalAssignmentWalker extends Lint.RuleWalker { private checkForAssignment(expression: ts.BinaryExpression) { if (isAssignmentToken(expression.operatorToken)) { - this.addFailure(this.createFailure(expression.getStart(), expression.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(expression, Rule.FAILURE_STRING); } } } diff --git a/src/rules/noConsecutiveBlankLinesRule.ts b/src/rules/noConsecutiveBlankLinesRule.ts index fffa18ee7bf..d1b440b666d 100644 --- a/src/rules/noConsecutiveBlankLinesRule.ts +++ b/src/rules/noConsecutiveBlankLinesRule.ts @@ -94,7 +94,7 @@ class NoConsecutiveBlankLinesWalker extends Lint.SkippableTokenAwareRuleWalker { .map((arr) => arr[0]) .forEach((startLineNum: number) => { let startCharPos = node.getPositionOfLineAndCharacter(startLineNum + 1, 0); - this.addFailure(this.createFailure(startCharPos, 1, failureMessage)); + this.addFailureAt(startCharPos, 1, failureMessage); }); } } diff --git a/src/rules/noConstructRule.ts b/src/rules/noConstructRule.ts index f687688f0e6..218820c62b6 100644 --- a/src/rules/noConstructRule.ts +++ b/src/rules/noConstructRule.ts @@ -56,8 +56,7 @@ class NoConstructWalker extends Lint.RuleWalker { const identifier = node.expression; const constructorName = identifier.text; if (NoConstructWalker.FORBIDDEN_CONSTRUCTORS.indexOf(constructorName) !== -1) { - const failure = this.createFailure(node.getStart(), identifier.getEnd() - node.getStart(), Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureAt(node.getStart(), identifier.getEnd() - node.getStart(), Rule.FAILURE_STRING); } } super.visitNewExpression(node); diff --git a/src/rules/noDebuggerRule.ts b/src/rules/noDebuggerRule.ts index 85703395895..9e7cfc4b496 100644 --- a/src/rules/noDebuggerRule.ts +++ b/src/rules/noDebuggerRule.ts @@ -43,7 +43,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NoDebuggerWalker extends Lint.RuleWalker { public visitDebuggerStatement(node: ts.Statement) { const debuggerKeywordNode = node.getChildAt(0); - this.addFailure(this.createFailure(debuggerKeywordNode.getStart(), debuggerKeywordNode.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(debuggerKeywordNode, Rule.FAILURE_STRING); super.visitDebuggerStatement(node); } } diff --git a/src/rules/noDefaultExportRule.ts b/src/rules/noDefaultExportRule.ts index ae9c7bcdd05..436018855d0 100644 --- a/src/rules/noDefaultExportRule.ts +++ b/src/rules/noDefaultExportRule.ts @@ -48,7 +48,7 @@ class NoDefaultExportWalker extends Lint.RuleWalker { public visitExportAssignment(node: ts.ExportAssignment) { const exportMember = node.getChildAt(1); if (exportMember != null && exportMember.kind === ts.SyntaxKind.DefaultKeyword) { - this.addFailure(this.createFailure(exportMember.getStart(), exportMember.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(exportMember, Rule.FAILURE_STRING); } super.visitExportAssignment(node); } @@ -61,7 +61,7 @@ class NoDefaultExportWalker extends Lint.RuleWalker { nodes.length === 2 && nodes[0].kind === ts.SyntaxKind.ExportKeyword && nodes[1].kind === ts.SyntaxKind.DefaultKeyword) { - this.addFailure(this.createFailure(nodes[1].getStart(), nodes[1].getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(nodes[1], Rule.FAILURE_STRING); } } super.visitNode(node); diff --git a/src/rules/noDuplicateVariableRule.ts b/src/rules/noDuplicateVariableRule.ts index 7dc9473bfdb..77989d8a18f 100644 --- a/src/rules/noDuplicateVariableRule.ts +++ b/src/rules/noDuplicateVariableRule.ts @@ -98,16 +98,11 @@ class NoDuplicateVariableWalker extends Lint.BlockScopeAwareRuleWalker<{}, Scope const currentBlockScope = this.getCurrentBlockScope(); if (currentBlockScope.varNames.indexOf(variableName) >= 0) { - this.addFailureOnIdentifier(variableIdentifier); + this.addFailureAtNode(variableIdentifier, Rule.FAILURE_STRING_FACTORY(variableIdentifier.text)); } else { currentBlockScope.varNames.push(variableName); } } - - private addFailureOnIdentifier(ident: ts.Identifier) { - const failureString = Rule.FAILURE_STRING_FACTORY(ident.text); - this.addFailure(this.createFailure(ident.getStart(), ident.getWidth(), failureString)); - } } class ScopeInfo { diff --git a/src/rules/noEmptyRule.ts b/src/rules/noEmptyRule.ts index d6dbbe1b695..f824cfe4e16 100644 --- a/src/rules/noEmptyRule.ts +++ b/src/rules/noEmptyRule.ts @@ -53,7 +53,7 @@ class BlockWalker extends Lint.RuleWalker { const isSkipped = this.ignoredBlocks.indexOf(node) !== -1; if (node.statements.length <= 0 && !hasCommentAfter && !hasCommentBefore && !isSkipped) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitBlock(node); diff --git a/src/rules/noEvalRule.ts b/src/rules/noEvalRule.ts index a2c4194dc0f..4b5279d2511 100644 --- a/src/rules/noEvalRule.ts +++ b/src/rules/noEvalRule.ts @@ -49,7 +49,7 @@ class NoEvalWalker extends Lint.RuleWalker { if (expression.kind === ts.SyntaxKind.Identifier) { const expressionName = ( expression).text; if (expressionName === "eval") { - this.addFailure(this.createFailure(expression.getStart(), expression.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(expression, Rule.FAILURE_STRING); } } diff --git a/src/rules/noForInArrayRule.ts b/src/rules/noForInArrayRule.ts index 2c335a58d9f..8d060519247 100644 --- a/src/rules/noForInArrayRule.ts +++ b/src/rules/noForInArrayRule.ts @@ -67,7 +67,7 @@ class NoForInArrayWalker extends Lint.ProgramAwareRuleWalker { /* tslint:enable:no-bitwise */ if (isArrayType || isStringType) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitForInStatement(node); diff --git a/src/rules/noInferrableTypesRule.ts b/src/rules/noInferrableTypesRule.ts index 79c918d9cc8..d5066325d9a 100644 --- a/src/rules/noInferrableTypesRule.ts +++ b/src/rules/noInferrableTypesRule.ts @@ -100,7 +100,7 @@ class NoInferrableTypesWalker extends Lint.RuleWalker { } if (failure != null) { - this.addFailure(this.createFailure(node.type.getStart(), node.type.getWidth(), Rule.FAILURE_STRING_FACTORY(failure))); + this.addFailureAtNode(node.type, Rule.FAILURE_STRING_FACTORY(failure)); } } } diff --git a/src/rules/noInferredEmptyObjectTypeRule.ts b/src/rules/noInferredEmptyObjectTypeRule.ts index b8a1bf923df..53ad3b74b3c 100644 --- a/src/rules/noInferredEmptyObjectTypeRule.ts +++ b/src/rules/noInferredEmptyObjectTypeRule.ts @@ -64,7 +64,7 @@ class NoInferredEmptyObjectTypeRule extends Lint.ProgramAwareRuleWalker { let typeArgs = objType.typeArguments as ts.ObjectType[]; typeArgs.forEach((a) => { if (this.isEmptyObjectInterface(a)) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EMPTY_INTERFACE_INSTANCE)); + this.addFailureAtNode(node, Rule.EMPTY_INTERFACE_INSTANCE); } }); } @@ -77,7 +77,7 @@ class NoInferredEmptyObjectTypeRule extends Lint.ProgramAwareRuleWalker { let callSig = this.checker.getResolvedSignature(node); let retType = this.checker.getReturnTypeOfSignature(callSig) as ts.TypeReference; if (this.isEmptyObjectInterface(retType)) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.EMPTY_INTERFACE_FUNCTION)); + this.addFailureAtNode(node, Rule.EMPTY_INTERFACE_FUNCTION); } } super.visitCallExpression(node); diff --git a/src/rules/noInternalModuleRule.ts b/src/rules/noInternalModuleRule.ts index f22c0d5ef9a..aa9b7aaa6f3 100644 --- a/src/rules/noInternalModuleRule.ts +++ b/src/rules/noInternalModuleRule.ts @@ -44,7 +44,7 @@ class NoInternalModuleWalker extends Lint.RuleWalker { public visitModuleDeclaration(node: ts.ModuleDeclaration) { if (this.isInternalModuleDeclaration(node)) { const start = this.getStartBeforeModule(node); - this.addFailure(this.createFailure(node.getStart() + start, "module".length, Rule.FAILURE_STRING)); + this.addFailureAt(node.getStart() + start, "module".length, Rule.FAILURE_STRING); } super.visitModuleDeclaration(node); } diff --git a/src/rules/noInvalidThisRule.ts b/src/rules/noInvalidThisRule.ts index 12e8f2c7ea0..8ee1d2d69f3 100644 --- a/src/rules/noInvalidThisRule.ts +++ b/src/rules/noInvalidThisRule.ts @@ -79,12 +79,12 @@ class NoInvalidThisWalker extends Lint.ScopeAwareRuleWalker { }); if (inClass === 0) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_OUTSIDE)); + this.addFailureAtNode(node, Rule.FAILURE_STRING_OUTSIDE); } const checkFuncInMethod = this.hasOption(DEPRECATED_OPTION_FUNCTION_IN_METHOD) || this.hasOption(OPTION_FUNCTION_IN_METHOD); if (checkFuncInMethod && inClass > 0 && inFunction > inClass) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING_INSIDE)); + this.addFailureAtNode(node, Rule.FAILURE_STRING_INSIDE); } } diff --git a/src/rules/noMagicNumbersRule.ts b/src/rules/noMagicNumbersRule.ts index f84fa611cd1..a2cc3ab7edb 100644 --- a/src/rules/noMagicNumbersRule.ts +++ b/src/rules/noMagicNumbersRule.ts @@ -85,7 +85,7 @@ class NoMagicNumbersWalker extends Lint.RuleWalker { if (node.kind === ts.SyntaxKind.NumericLiteral && !Rule.ALLOWED_NODES[node.parent.kind] || isUnary) { let text = node.getText(); if (!this.allowed[text]) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } } if (!isUnary) { diff --git a/src/rules/noMergeableNamespaceRule.ts b/src/rules/noMergeableNamespaceRule.ts index 680dc278f31..a9ee9a9fb7f 100644 --- a/src/rules/noMergeableNamespaceRule.ts +++ b/src/rules/noMergeableNamespaceRule.ts @@ -61,7 +61,7 @@ class NoMergeableNamespaceWalker extends Lint.RuleWalker { if (highlights == null || highlights[0].highlightSpans.length > 1) { const failureString = Rule.failureStringFactory(name, this.findLocationToMerge(position, highlights[0].highlightSpans)); - this.addFailure(this.createFailure(position, name.length, failureString)); + this.addFailureAt(position, name.length, failureString); } } diff --git a/src/rules/noNamespaceRule.ts b/src/rules/noNamespaceRule.ts index 7625969889b..59ca43816f5 100644 --- a/src/rules/noNamespaceRule.ts +++ b/src/rules/noNamespaceRule.ts @@ -79,6 +79,6 @@ class NoNamespaceWalker extends Lint.RuleWalker { return; } - this.addFailure(this.createFailure(decl.getStart(), decl.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(decl, Rule.FAILURE_STRING); } } diff --git a/src/rules/noNullKeywordRule.ts b/src/rules/noNullKeywordRule.ts index 6592c1640d1..44f66987331 100644 --- a/src/rules/noNullKeywordRule.ts +++ b/src/rules/noNullKeywordRule.ts @@ -48,7 +48,7 @@ class NullWalker extends Lint.RuleWalker { public visitNode(node: ts.Node) { super.visitNode(node); if (node.kind === ts.SyntaxKind.NullKeyword && !isPartOfType(node)) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } } } diff --git a/src/rules/noParameterPropertiesRule.ts b/src/rules/noParameterPropertiesRule.ts index d4ee70be800..e25612ac970 100644 --- a/src/rules/noParameterPropertiesRule.ts +++ b/src/rules/noParameterPropertiesRule.ts @@ -51,8 +51,7 @@ export class NoParameterPropertiesWalker extends Lint.RuleWalker { if (parameter.modifiers != null && parameter.modifiers.length > 0) { const errorMessage = Rule.FAILURE_STRING_FACTORY((parameter.name as ts.Identifier).text); const lastModifier = parameter.modifiers[parameter.modifiers.length - 1]; - const position = lastModifier.getEnd() - parameter.getStart(); - this.addFailure(this.createFailure(parameter.getStart(), position, errorMessage)); + this.addFailureFromStartToEnd(parameter.getStart(), lastModifier.getEnd(), errorMessage); } } super.visitConstructorDeclaration(node); diff --git a/src/rules/noReferenceRule.ts b/src/rules/noReferenceRule.ts index e20e38744c3..8479c002a1f 100644 --- a/src/rules/noReferenceRule.ts +++ b/src/rules/noReferenceRule.ts @@ -45,7 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NoReferenceWalker extends Lint.RuleWalker { public visitSourceFile(node: ts.SourceFile) { for (let ref of node.referencedFiles) { - this.addFailure(this.createFailure(ref.pos, ref.end - ref.pos, Rule.FAILURE_STRING)); + this.addFailureFromStartToEnd(ref.pos, ref.end, Rule.FAILURE_STRING); } } } diff --git a/src/rules/noRequireImportsRule.ts b/src/rules/noRequireImportsRule.ts index 45031cc4fc8..7a803e2883e 100644 --- a/src/rules/noRequireImportsRule.ts +++ b/src/rules/noRequireImportsRule.ts @@ -45,7 +45,7 @@ class NoRequireImportsWalker extends Lint.RuleWalker { if (node.arguments != null && node.expression != null) { const callExpressionText = node.expression.getText(this.getSourceFile()); if (callExpressionText === "require") { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } } super.visitCallExpression(node); @@ -54,7 +54,7 @@ class NoRequireImportsWalker extends Lint.RuleWalker { public visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration) { const {moduleReference} = node; if (moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) { - this.addFailure(this.createFailure(moduleReference.getStart(), moduleReference.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(moduleReference, Rule.FAILURE_STRING); } super.visitImportEqualsDeclaration(node); } diff --git a/src/rules/noShadowedVariableRule.ts b/src/rules/noShadowedVariableRule.ts index 6b2ffa0057e..8e9965e6116 100644 --- a/src/rules/noShadowedVariableRule.ts +++ b/src/rules/noShadowedVariableRule.ts @@ -156,7 +156,7 @@ class NoShadowedVariableWalker extends Lint.BlockScopeAwareRuleWalker 0 && ((switchClauses.length - 1) > i)) { if (!isFallThroughAllowed(switchClauses[i + 1])) { - this.addFailure(this.createFailure( - switchClauses[i + 1].getStart(), - "case".length, - `${Rule.FAILURE_STRING_PART}'case'`, - )); + this.addFailureAt(switchClauses[i + 1].getStart(), "case".length, `${Rule.FAILURE_STRING_PART}'case'`); } } } else { // case statement falling through a default if (isFallingThrough && !isFallThroughAllowed(child)) { - const failureString = Rule.FAILURE_STRING_PART + "'default'"; - this.addFailure(this.createFailure(switchClauses[i].getStart(), "default".length, failureString)); + this.addFailureAt(switchClauses[i].getStart(), "default".length, Rule.FAILURE_STRING_PART + "'default'"); } } }); diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index 51cc186c4f2..2e25a31f96e 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -57,9 +57,7 @@ class NoTrailingWhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker { if (scanner.getToken() === ts.SyntaxKind.NewLineTrivia) { if (lastSeenWasWhitespace) { - const width = scanner.getStartPos() - lastSeenWhitespacePosition; - const failure = this.createFailure(lastSeenWhitespacePosition, width, Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureFromStartToEnd(lastSeenWhitespacePosition, scanner.getStartPos(), Rule.FAILURE_STRING); } lastSeenWasWhitespace = false; } else if (scanner.getToken() === ts.SyntaxKind.WhitespaceTrivia) { diff --git a/src/rules/noUnsafeFinallyRule.ts b/src/rules/noUnsafeFinallyRule.ts index bfe8669a5a7..c0bfba6d5e3 100644 --- a/src/rules/noUnsafeFinallyRule.ts +++ b/src/rules/noUnsafeFinallyRule.ts @@ -94,8 +94,7 @@ class NoReturnInFinallyScopeAwareWalker extends Lint.ScopeAwareRuleWalker 1) || this.isIgnored(name); } - private fail(type: string, name: string, position: number, replacements?: Lint.Replacement[]) { + private fail(type: string, name: string, position: number, replacements: Lint.Replacement[]) { let fix: Lint.Fix; if (replacements && replacements.length) { fix = new Lint.Fix(Rule.metadata.ruleName, replacements); diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index c626729f72c..c3745c56c86 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -127,7 +127,7 @@ class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { const identifierName = ( expression).text; if (identifierName === "require") { // if we're calling (invoking) require, then it's not part of an import statement - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } } diff --git a/src/rules/objectLiteralKeyQuotesRule.ts b/src/rules/objectLiteralKeyQuotesRule.ts index 413581f298b..9de7f920a81 100644 --- a/src/rules/objectLiteralKeyQuotesRule.ts +++ b/src/rules/objectLiteralKeyQuotesRule.ts @@ -46,7 +46,7 @@ export class Rule extends Lint.Rules.AbstractRule { * \`"always"\`: Property names should always be quoted. (This is the default.) * \`"as-needed"\`: Only property names which require quotes may be quoted (e.g. those with spaces in them). * \`"consistent"\`: Property names should either all be quoted or unquoted. - * \`"consistent-as-needed"\`: If any property name requires quotes, then all properties must be quoted. Otherwise, no + * \`"consistent-as-needed"\`: If any property name requires quotes, then all properties must be quoted. Otherwise, no property names may be quoted. For ES6, computed property names (\`{[name]: value}\`) and methods (\`{foo() {}}\`) never need @@ -151,7 +151,7 @@ class ObjectLiteralKeyQuotesWalker extends Lint.RuleWalker { const hasQuotedProperties = state.hasQuotesNeededProperty || state.quotesNotNeededProperties.length > 0; const hasUnquotedProperties = state.unquotedProperties.length > 0; if (hasQuotedProperties && hasUnquotedProperties) { - this.addFailure(this.createFailure(node.getStart(), 1, Rule.INCONSISTENT_PROPERTY)); + this.addFailureAt(node.getStart(), 1, Rule.INCONSISTENT_PROPERTY); } } diff --git a/src/rules/objectLiteralShorthandRule.ts b/src/rules/objectLiteralShorthandRule.ts index aefc93181a8..db33563ad64 100644 --- a/src/rules/objectLiteralShorthandRule.ts +++ b/src/rules/objectLiteralShorthandRule.ts @@ -32,7 +32,7 @@ class ObjectLiteralShorthandWalker extends Lint.RuleWalker { if (name.kind === ts.SyntaxKind.Identifier && value.kind === ts.SyntaxKind.Identifier && name.getText() === value.getText()) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.LONGHAND_PROPERTY)); + this.addFailureAtNode(node, Rule.LONGHAND_PROPERTY); } if (value.kind === ts.SyntaxKind.FunctionExpression) { @@ -41,7 +41,7 @@ class ObjectLiteralShorthandWalker extends Lint.RuleWalker { return; // named function expressions are OK. } - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.LONGHAND_METHOD)); + this.addFailureAtNode(node, Rule.LONGHAND_METHOD); } super.visitPropertyAssignment(node); diff --git a/src/rules/objectLiteralSortKeysRule.ts b/src/rules/objectLiteralSortKeysRule.ts index 78dc7d96efa..ad2a2f66815 100644 --- a/src/rules/objectLiteralSortKeysRule.ts +++ b/src/rules/objectLiteralSortKeysRule.ts @@ -74,7 +74,7 @@ class ObjectLiteralSortKeysWalker extends Lint.RuleWalker { const key = keyNode.text; if (key < lastSortedKey) { const failureString = Rule.FAILURE_STRING_FACTORY(key); - this.addFailure(this.createFailure(keyNode.getStart(), keyNode.getWidth(), failureString)); + this.addFailureAtNode(keyNode, failureString); this.sortedStateStack[this.sortedStateStack.length - 1] = false; } else { this.lastSortedKeyStack[this.lastSortedKeyStack.length - 1] = key; diff --git a/src/rules/oneLineRule.ts b/src/rules/oneLineRule.ts index bad0235d9d4..2f51852d73e 100644 --- a/src/rules/oneLineRule.ts +++ b/src/rules/oneLineRule.ts @@ -87,8 +87,7 @@ class OneLineWalker extends Lint.RuleWalker { const thenStatementEndLine = sourceFile.getLineAndCharacterOfPosition(thenStatement.getEnd()).line; const elseKeywordLine = sourceFile.getLineAndCharacterOfPosition(elseKeyword.getStart()).line; if (thenStatementEndLine !== elseKeywordLine) { - const failure = this.createFailure(elseKeyword.getStart(), elseKeyword.getWidth(), Rule.ELSE_FAILURE_STRING); - this.addFailure(failure); + this.addFailureAtNode(elseKeyword, Rule.ELSE_FAILURE_STRING); } } } @@ -121,8 +120,7 @@ class OneLineWalker extends Lint.RuleWalker { const tryClosingBraceLine = sourceFile.getLineAndCharacterOfPosition(tryClosingBrace.getEnd()).line; const catchKeywordLine = sourceFile.getLineAndCharacterOfPosition(catchKeyword.getStart()).line; if (tryClosingBraceLine !== catchKeywordLine) { - const failure = this.createFailure(catchKeyword.getStart(), catchKeyword.getWidth(), Rule.CATCH_FAILURE_STRING); - this.addFailure(failure); + this.addFailureAtNode(catchKeyword, Rule.CATCH_FAILURE_STRING); } } @@ -136,8 +134,7 @@ class OneLineWalker extends Lint.RuleWalker { const closingBraceLine = sourceFile.getLineAndCharacterOfPosition(closingBrace.getEnd()).line; const finallyKeywordLine = sourceFile.getLineAndCharacterOfPosition(finallyKeyword.getStart()).line; if (closingBraceLine !== finallyKeywordLine) { - const failure = this.createFailure(finallyKeyword.getStart(), finallyKeyword.getWidth(), Rule.FINALLY_FAILURE_STRING); - this.addFailure(failure); + this.addFailureAtNode(finallyKeyword, Rule.FINALLY_FAILURE_STRING); } } } @@ -296,16 +293,16 @@ class OneLineWalker extends Lint.RuleWalker { const sourceFile = previousNode.getSourceFile(); const previousNodeLine = sourceFile.getLineAndCharacterOfPosition(previousNode.getEnd()).line; const openBraceLine = sourceFile.getLineAndCharacterOfPosition(openBraceToken.getStart()).line; - let failure: Lint.RuleFailure; + let failure: string; if (this.hasOption(OPTION_BRACE) && previousNodeLine !== openBraceLine) { - failure = this.createFailure(openBraceToken.getStart(), openBraceToken.getWidth(), Rule.BRACE_FAILURE_STRING); + failure = Rule.BRACE_FAILURE_STRING; } else if (this.hasOption(OPTION_WHITESPACE) && previousNode.getEnd() === openBraceToken.getStart()) { - failure = this.createFailure(openBraceToken.getStart(), openBraceToken.getWidth(), Rule.WHITESPACE_FAILURE_STRING); + failure = Rule.WHITESPACE_FAILURE_STRING; } if (failure) { - this.addFailure(failure); + this.addFailureAtNode(openBraceToken, failure); } } } diff --git a/src/rules/oneVariablePerDeclarationRule.ts b/src/rules/oneVariablePerDeclarationRule.ts index f8c056ba4e9..b33ae82e28b 100644 --- a/src/rules/oneVariablePerDeclarationRule.ts +++ b/src/rules/oneVariablePerDeclarationRule.ts @@ -58,7 +58,7 @@ class OneVariablePerDeclarationWalker extends Lint.RuleWalker { const { declarationList } = node; if (declarationList.declarations.length > 1) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitVariableStatement(node); @@ -72,7 +72,7 @@ class OneVariablePerDeclarationWalker extends Lint.RuleWalker { && initializer != null && initializer.kind === ts.SyntaxKind.VariableDeclarationList && initializer.declarations.length > 1) { - this.addFailure(this.createFailure(initializer.getStart(), initializer.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(initializer, Rule.FAILURE_STRING); } super.visitForStatement(node); diff --git a/src/rules/onlyArrowFunctionsRule.ts b/src/rules/onlyArrowFunctionsRule.ts index b04fb132121..1c2437dca34 100644 --- a/src/rules/onlyArrowFunctionsRule.ts +++ b/src/rules/onlyArrowFunctionsRule.ts @@ -73,7 +73,7 @@ class OnlyArrowFunctionsWalker extends Lint.RuleWalker { private failUnlessExempt(node: ts.FunctionLikeDeclaration) { if (!functionIsExempt(node)) { - this.addFailure(this.createFailure(node.getStart(), "function".length, Rule.FAILURE_STRING)); + this.addFailureAt(node.getStart(), "function".length, Rule.FAILURE_STRING); } } } diff --git a/src/rules/orderedImportsRule.ts b/src/rules/orderedImportsRule.ts index 8b28251131d..ce7bb8fac5b 100644 --- a/src/rules/orderedImportsRule.ts +++ b/src/rules/orderedImportsRule.ts @@ -177,8 +177,7 @@ class OrderedImportsWalker extends Lint.RuleWalker { if (previousSource && compare(source, previousSource) === -1) { this.lastFix = new Lint.Fix(Rule.metadata.ruleName, []); - const ruleFailure = this.createFailure(node.getStart(), node.getWidth(), Rule.IMPORT_SOURCES_UNORDERED, this.lastFix); - this.addFailure(ruleFailure); + this.addFailureAtNode(node, Rule.IMPORT_SOURCES_UNORDERED, this.lastFix); } super.visitImportDeclaration(node); @@ -203,12 +202,7 @@ class OrderedImportsWalker extends Lint.RuleWalker { } this.lastFix = new Lint.Fix(Rule.metadata.ruleName, []); - const ruleFailure = this.createFailure( - a.getStart(), - b.getEnd() - a.getStart(), - Rule.NAMED_IMPORTS_UNORDERED, - this.lastFix); - this.addFailure(ruleFailure); + this.addFailureFromStartToEnd(a.getStart(), b.getEnd(), Rule.NAMED_IMPORTS_UNORDERED, this.lastFix); } super.visitNamedImports(node); @@ -270,7 +264,7 @@ class ImportsBlock { }); } - // replaces the named imports on the most recent import declaration + // replaces the named imports on the most recent import declaration public replaceNamedImports(fileOffset: number, length: number, replacement: string) { const importDeclaration = this.getLastImportDeclaration(); if (importDeclaration == null) { @@ -294,7 +288,7 @@ class ImportsBlock { return this.getLastImportDeclaration().sourcePath; } - // creates a Lint.Replacement object with ordering fixes for the entire block + // creates a Lint.Replacement object with ordering fixes for the entire block public getReplacement() { if (this.importDeclarations.length === 0) { return null; @@ -306,7 +300,7 @@ class ImportsBlock { return new Lint.Replacement(start, end - start, fixedText); } - // gets the offset immediately after the end of the previous declaration to include comment above + // gets the offset immediately after the end of the previous declaration to include comment above private getStartOffset(node: ts.ImportDeclaration) { if (this.importDeclarations.length === 0) { return node.getStart(); diff --git a/src/rules/preferConstRule.ts b/src/rules/preferConstRule.ts index af28aa8d87e..ba49ffdef39 100644 --- a/src/rules/preferConstRule.ts +++ b/src/rules/preferConstRule.ts @@ -63,12 +63,7 @@ class PreferConstWalker extends Lint.BlockScopeAwareRuleWalker<{}, ScopeInfo> { fix = new Lint.Fix(Rule.metadata.ruleName, [replacement]); seenLetStatements[usage.letStatement.getStart().toString()] = true; } - this.addFailure(this.createFailure( - usage.identifier.getStart(), - usage.identifier.getWidth(), - Rule.FAILURE_STRING_FACTORY(usage.identifier.text), - fix, - )); + this.addFailureAtNode(usage.identifier, Rule.FAILURE_STRING_FACTORY(usage.identifier.text), fix); } } diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index d1ff6c8f9ac..f9496287eb8 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -75,9 +75,7 @@ class PreferForOfWalker extends Lint.RuleWalker { if (indexVariableName != null) { const incrementorState = this.incrementorMap[indexVariableName]; if (incrementorState.onlyArrayReadAccess) { - const length = incrementorState.forLoopEndPosition - node.getStart(); - const failure = this.createFailure(node.getStart(), length, Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureFromStartToEnd(node.getStart(), incrementorState.forLoopEndPosition, Rule.FAILURE_STRING); } // remove current `for` loop state diff --git a/src/rules/promiseFunctionAsyncRule.ts b/src/rules/promiseFunctionAsyncRule.ts index 4582d11bdaf..5303df0b02f 100644 --- a/src/rules/promiseFunctionAsyncRule.ts +++ b/src/rules/promiseFunctionAsyncRule.ts @@ -79,7 +79,7 @@ class PromiseAsyncWalker extends Lint.ProgramAwareRuleWalker { : node.getWidth(); if (isPromise && !isAsync) { - this.addFailure(this.createFailure(node.getStart(), signatureEnd, Rule.FAILURE_STRING)); + this.addFailureAt(node.getStart(), signatureEnd, Rule.FAILURE_STRING); } } } diff --git a/src/rules/quotemarkRule.ts b/src/rules/quotemarkRule.ts index cc99af3a18a..ee21d37164e 100644 --- a/src/rules/quotemarkRule.ts +++ b/src/rules/quotemarkRule.ts @@ -121,7 +121,7 @@ class QuotemarkWalker extends Lint.RuleWalker { + expectedQuoteMark; const fix = new Lint.Fix(Rule.metadata.ruleName, [ new Lint.Replacement(position, width, newText) ]); - this.addFailure(this.createFailure(position, width, failureMessage, fix)); + this.addFailureAt(position, width, failureMessage, fix); } } diff --git a/src/rules/radixRule.ts b/src/rules/radixRule.ts index e35521fc226..99dcdfcd641 100644 --- a/src/rules/radixRule.ts +++ b/src/rules/radixRule.ts @@ -25,7 +25,7 @@ export class Rule extends Lint.Rules.AbstractRule { ruleName: "radix", description: "Requires the radix parameter to be specified when calling `parseInt`.", rationale: Lint.Utils.dedent` - From [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt): + From [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt): > Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior. > Different implementations produce different results when a radix is not specified, usually defaulting the value to 10.`, optionsDescription: "Not configurable.", @@ -51,7 +51,7 @@ class RadixWalker extends Lint.RuleWalker { if (expression.kind === ts.SyntaxKind.Identifier && node.getFirstToken().getText() === "parseInt" && node.arguments.length < 2) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitCallExpression(node); diff --git a/src/rules/restrictPlusOperandsRule.ts b/src/rules/restrictPlusOperandsRule.ts index ea4542ccc06..82b9f7a9a99 100644 --- a/src/rules/restrictPlusOperandsRule.ts +++ b/src/rules/restrictPlusOperandsRule.ts @@ -52,7 +52,7 @@ class RestrictPlusOperandsWalker extends Lint.ProgramAwareRuleWalker { const position = node.getStart(); if (leftType === "invalid" || rightType === "invalid" || leftType !== rightType) { - this.addFailure(this.createFailure(position, width, Rule.INVALID_TYPES_ERROR)); + this.addFailureAt(position, width, Rule.INVALID_TYPES_ERROR); } } diff --git a/src/rules/semicolonRule.ts b/src/rules/semicolonRule.ts index b9be0599be4..ec4541b8d1c 100644 --- a/src/rules/semicolonRule.ts +++ b/src/rules/semicolonRule.ts @@ -175,7 +175,7 @@ class SemicolonWalker extends Lint.RuleWalker { const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.appendText(failureStart, ";"), ]); - this.addFailure(this.createFailure(failureStart, 0, Rule.FAILURE_STRING_MISSING, fix)); + this.addFailureAt(failureStart, 0, Rule.FAILURE_STRING_MISSING, fix); } else if (never && hasSemicolon) { const scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, sourceFile.text); scanner.setTextPos(position); @@ -191,7 +191,7 @@ class SemicolonWalker extends Lint.RuleWalker { const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.deleteText(failureStart, 1), ]); - this.addFailure(this.createFailure(failureStart, 1, Rule.FAILURE_STRING_UNNECESSARY, fix)); + this.addFailureAt(failureStart, 1, Rule.FAILURE_STRING_UNNECESSARY, fix); } } } diff --git a/src/rules/strictBooleanExpressionsRule.ts b/src/rules/strictBooleanExpressionsRule.ts index 720de93227e..135f77cfa23 100644 --- a/src/rules/strictBooleanExpressionsRule.ts +++ b/src/rules/strictBooleanExpressionsRule.ts @@ -64,14 +64,14 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { let rhsType = this.checker.getTypeAtLocation(rhsExpression); if (!this.isBooleanType(lhsType)) { if (lhsExpression.kind !== ts.SyntaxKind.BinaryExpression) { - this.addFailure(this.createFailure(lhsExpression.getStart(), lhsExpression.getWidth(), Rule.BINARY_EXPRESSION_ERROR)); + this.addFailureAtNode(lhsExpression, Rule.BINARY_EXPRESSION_ERROR); } else { this.visitBinaryExpression( lhsExpression); } } if (!this.isBooleanType(rhsType)) { if (rhsExpression.kind !== ts.SyntaxKind.BinaryExpression) { - this.addFailure(this.createFailure(rhsExpression.getStart(), rhsExpression.getWidth(), Rule.BINARY_EXPRESSION_ERROR)); + this.addFailureAtNode(rhsExpression, Rule.BINARY_EXPRESSION_ERROR); } else { this.visitBinaryExpression( rhsExpression); } @@ -86,7 +86,7 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { let expr = node.operand; let expType = this.checker.getTypeAtLocation(expr); if (!this.isBooleanType(expType)) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.UNARY_EXPRESSION_ERROR)); + this.addFailureAtNode(node, Rule.UNARY_EXPRESSION_ERROR); } } super.visitPrefixUnaryExpression(node); @@ -111,7 +111,7 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { let cexp = node.condition; let expType = this.checker.getTypeAtLocation(cexp); if (!this.isBooleanType(expType)) { - this.addFailure(this.createFailure(cexp.getStart(), cexp.getWidth(), Rule.CONDITIONAL_EXPRESSION_ERROR)); + this.addFailureAtNode(cexp, Rule.CONDITIONAL_EXPRESSION_ERROR); } super.visitConditionalExpression(node); } @@ -120,7 +120,7 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { let cexp = node.condition; let expType = this.checker.getTypeAtLocation(cexp); if (!this.isBooleanType(expType)) { - this.addFailure(this.createFailure(cexp.getStart(), cexp.getWidth(), `For ${Rule.STATEMENT_ERROR}`)); + this.addFailureAtNode(cexp, `For ${Rule.STATEMENT_ERROR}`); } super.visitForStatement(node); } @@ -129,19 +129,7 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { let bexp = node.expression; let expType = this.checker.getTypeAtLocation(bexp); if (!this.isBooleanType(expType)) { - switch (node.kind) { - case ts.SyntaxKind.IfStatement: - this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `If ${Rule.STATEMENT_ERROR}`)); - break; - case ts.SyntaxKind.DoStatement: - this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `Do-While ${Rule.STATEMENT_ERROR}`)); - break; - case ts.SyntaxKind.WhileStatement: - this.addFailure(this.createFailure(bexp.getStart(), bexp.getWidth(), `While ${Rule.STATEMENT_ERROR}`)); - break; - default: - throw new Error("Unknown Syntax Kind"); - } + this.addFailureAtNode(bexp, `${failureTextForKind(node.kind)} ${Rule.STATEMENT_ERROR}`); } } @@ -149,3 +137,16 @@ class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker { return utils.isTypeFlagSet(btype, ts.TypeFlags.BooleanLike); } } + +function failureTextForKind(kind: ts.SyntaxKind) { + switch (kind) { + case ts.SyntaxKind.IfStatement: + return "If"; + case ts.SyntaxKind.DoStatement: + return "Do-While"; + case ts.SyntaxKind.WhileStatement: + return "While"; + default: + throw new Error("Unknown Syntax Kind"); + } +} diff --git a/src/rules/switchDefaultRule.ts b/src/rules/switchDefaultRule.ts index 0d8524c9c61..8e4743f2076 100644 --- a/src/rules/switchDefaultRule.ts +++ b/src/rules/switchDefaultRule.ts @@ -44,7 +44,7 @@ export class SwitchDefaultWalker extends Lint.RuleWalker { const hasDefaultCase = node.caseBlock.clauses.some((clause) => clause.kind === ts.SyntaxKind.DefaultClause); if (!hasDefaultCase) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); + this.addFailureAtNode(node, Rule.FAILURE_STRING); } super.visitSwitchStatement(node); diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 84e949cafa1..3f00d1e035f 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -247,13 +247,13 @@ class TrailingCommaWalker extends Lint.RuleWalker { const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.deleteText(failureStart, 1), ]); - this.addFailure(this.createFailure(failureStart, 1, Rule.FAILURE_STRING_NEVER, fix)); + this.addFailureAt(failureStart, 1, Rule.FAILURE_STRING_NEVER, fix); } else if (!hasTrailingComma && option === "always") { const failureStart = lastGrandChild.getEnd(); const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.appendText(failureStart, ","), ]); - this.addFailure(this.createFailure(failureStart - 1, 1, Rule.FAILURE_STRING_ALWAYS, fix)); + this.addFailureAt(failureStart - 1, 1, Rule.FAILURE_STRING_ALWAYS, fix); } } } diff --git a/src/rules/tripleEqualsRule.ts b/src/rules/tripleEqualsRule.ts index 24b04cd9622..3077b2c9556 100644 --- a/src/rules/tripleEqualsRule.ts +++ b/src/rules/tripleEqualsRule.ts @@ -74,10 +74,10 @@ class ComparisonWalker extends Lint.RuleWalker { private handleOperatorToken(position: number, operator: ts.SyntaxKind) { switch (operator) { case ts.SyntaxKind.EqualsEqualsToken: - this.addFailure(this.createFailure(position, ComparisonWalker.COMPARISON_OPERATOR_WIDTH, Rule.EQ_FAILURE_STRING)); + this.addFailureAt(position, ComparisonWalker.COMPARISON_OPERATOR_WIDTH, Rule.EQ_FAILURE_STRING); break; case ts.SyntaxKind.ExclamationEqualsToken: - this.addFailure(this.createFailure(position, ComparisonWalker.COMPARISON_OPERATOR_WIDTH, Rule.NEQ_FAILURE_STRING)); + this.addFailureAt(position, ComparisonWalker.COMPARISON_OPERATOR_WIDTH, Rule.NEQ_FAILURE_STRING); break; default: break; diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index fb8ab0f20ab..876f7633bab 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -210,8 +210,7 @@ class TypedefWalker extends Lint.RuleWalker { if (name != null && name.kind === ts.SyntaxKind.Identifier) { ns = `: '${( name).text}'`; } - let failure = this.createFailure(location, 1, "expected " + option + ns + " to have a typedef"); - this.addFailure(failure); + this.addFailureAt(location, 1, "expected " + option + ns + " to have a typedef"); } } } diff --git a/src/rules/typedefWhitespaceRule.ts b/src/rules/typedefWhitespaceRule.ts index c7cbb094262..acd5d560f47 100644 --- a/src/rules/typedefWhitespaceRule.ts +++ b/src/rules/typedefWhitespaceRule.ts @@ -297,7 +297,7 @@ class TypedefWhitespaceWalker extends Lint.RuleWalker { (optionValue === "onespace" || optionValue === "space"); if (isFailure) { - this.addFailure(this.createFailure(failurePos, 1, message)); + this.addFailureAt(failurePos, 1, message); } } } diff --git a/src/rules/useIsnanRule.ts b/src/rules/useIsnanRule.ts index 651b30f4265..e768678c14c 100644 --- a/src/rules/useIsnanRule.ts +++ b/src/rules/useIsnanRule.ts @@ -46,7 +46,7 @@ class UseIsnanRuleWalker extends Lint.RuleWalker { protected visitBinaryExpression(node: ts.BinaryExpression): void { if ((this.isExpressionNaN(node.left) || this.isExpressionNaN(node.right)) && node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING + node.getText())); + this.addFailureAtNode(node, Rule.FAILURE_STRING + node.getText()); } super.visitBinaryExpression(node); } diff --git a/src/rules/variableNameRule.ts b/src/rules/variableNameRule.ts index 77165f54efa..cd50efa83c6 100644 --- a/src/rules/variableNameRule.ts +++ b/src/rules/variableNameRule.ts @@ -146,7 +146,7 @@ class VariableNameWalker extends Lint.RuleWalker { } if (this.shouldCheckFormat && !this.isCamelCase(variableName) && !isUpperCase(variableName)) { - this.addFailure(this.createFailure(name.getStart(), name.getWidth(), Rule.FORMAT_FAILURE)); + this.addFailureAtNode(name, Rule.FORMAT_FAILURE); } } @@ -154,7 +154,7 @@ class VariableNameWalker extends Lint.RuleWalker { const variableName = name.text; if (this.shouldBanKeywords && BANNED_KEYWORDS.indexOf(variableName) !== -1) { - this.addFailure(this.createFailure(name.getStart(), name.getWidth(), Rule.KEYWORD_FAILURE)); + this.addFailureAtNode(name, Rule.KEYWORD_FAILURE); } } diff --git a/src/rules/whitespaceRule.ts b/src/rules/whitespaceRule.ts index 3e9bc272042..ad1aba19d8f 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -87,8 +87,7 @@ class WhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker { if (tokenKind === ts.SyntaxKind.WhitespaceTrivia || tokenKind === ts.SyntaxKind.NewLineTrivia) { prevTokenShouldBeFollowedByWhitespace = false; } else if (prevTokenShouldBeFollowedByWhitespace) { - const failure = this.createFailure(startPos, 1, Rule.FAILURE_STRING); - this.addFailure(failure); + this.addFailureAt(startPos, 1, Rule.FAILURE_STRING); prevTokenShouldBeFollowedByWhitespace = false; } @@ -260,7 +259,7 @@ class WhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker { if (nextTokenType !== ts.SyntaxKind.WhitespaceTrivia && nextTokenType !== ts.SyntaxKind.NewLineTrivia && nextTokenType !== ts.SyntaxKind.EndOfFileToken) { - this.addFailure(this.createFailure(position, 1, Rule.FAILURE_STRING)); + this.addFailureAt(position, 1, Rule.FAILURE_STRING); } } } From ddcd51075c0509f730d9446ae1a610ab73e1312e Mon Sep 17 00:00:00 2001 From: tdsmithATabc Date: Wed, 14 Dec 2016 16:07:42 -0600 Subject: [PATCH 40/48] Remove extra bracket in completed-docs rule config example (#1873) --- src/rules/completedDocsRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index 2d32d4ec6a0..89b6358e362 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -35,7 +35,7 @@ export class Rule extends Lint.Rules.TypedRule { enum: ["classes", "functions", "methods", "properties"], }, }, - optionExamples: ["true", `[true, ["classes", "functions"]`], + optionExamples: ["true", `[true, "classes", "functions"]`], type: "style", typescriptOnly: false, }; From 8f1daab8ed1b86559638a507b014198685546185 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 14 Dec 2016 20:39:44 -0800 Subject: [PATCH 41/48] Update docs (`npm run docs`) (#1847) From 2218cfc63b8282b9082a89ce03b395c3d9daa4cd Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Thu, 15 Dec 2016 16:03:57 -0500 Subject: [PATCH 42/48] Prepare release 4.1.0 (#1875) Update docs and changelog --- CHANGELOG.md | 145 ++++++++++++++++++ docs/_data/rules.json | 19 +-- docs/rules/ban/index.html | 4 +- docs/rules/completed-docs/index.html | 2 +- .../object-literal-key-quotes/index.html | 2 +- docs/rules/only-arrow-functions/index.html | 9 +- docs/rules/ordered-imports/index.html | 1 + docs/rules/radix/index.html | 2 +- package.json | 2 +- src/linter.ts | 2 +- 10 files changed, 169 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30242ae6abc..b3eed246d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,55 @@ Change Log === +v4.1.0 +--- + +* [new-rule] `prefer-const` (#1801) +* [new-rule] `strict-boolean-expressions` (#1820) +* [new-rule] `no-magic-numbers` (#1799) +* [new-rule] `import-blacklist` (#1841) +* [new-rule] `promise-functions-async` (#1779) +* [new-rule] `no-inferred-empty-object-type`: a type must be specified when using a generic class/function/etc (#1821) +* [new-rule-option] `allow-named-functions` added to `only-arrow-functions` (#1857) +* [new-fixer] `prefer-const` (#1801) +* [new-fixer] `quotemark` (#1790) +* [new-formatter] `code-frame` formatter shows you the error in context (#1819) +* [enhancement] `no-internal-module` failures highlight less text (#1781) +* [enhancement] Avoid auto-fixing errors that would result in compilation errors for rules that use type-check (#1608) +* [rule-change] `only-arrow-functions` will allow functions with a `this` parameter (#1597) +* [bugfix] `no-use-before-declare` false positive on named import (#1620) +* [bugfix] `prefer-for-of` was showing false positive when the element is assigned (#1813) +* [bugfix] The command line argument `type-check` was swallowing the next argument (#1783) +* [bugfix] `tslint:disable-line` was re-enabling `tslint:disable` (#1634) +* [bugfix] `adjacent-overload-signatures` did not work for constructors (#1800) +* [bugfix] `checkstyle` formatter was reporting errors under one file (#1811) +* [bugfix] `trailing-comma` was applied to parameter lists (#1775) +* [api] CLI logic moved into API friendly class (#1688) + +Thanks to our contributors! + +* Alex Eagle +* Andrii Dieiev +* Andy Hanson +* Art Chaidarun +* Donald Pipowitch +* Feisal Ahmad +* Josh Goldberg +* Klaus Meinhardt +* Maciej Sypień +* Mohsen Azimi +* Ryan Lester +* Simon Schick +* Subhash Sharma +* Timothy Slatcher +* Yaroslav Admin +* Yuichi Nukiyama +* tdsmithATabc +* @wmrowan + v4.0.2 --- + * [enhancement] Don't exit when a rule can't be found. Print as a warning instead (#1771) * [api-change] Allow 3rd party apps to see exception when the config is invalid (#1764) * [bugfix] Don't flag a property named as empty string as not needing quotes in an object literal (#1762) @@ -15,14 +62,17 @@ Thanks to our contributors! v4.0.1 --- + * [bugfix] Removed `no-unused-variable` rule from recommended config, as it was causing spurious deprecation warnings. v4.0.0-dev.2 --- + * Include latest v4.0.0 changes v4.0.0 --- + * **BREAKING CHANGES** * [api-change] Minor changes to the library API. See this PR for changes and upgrade instructions (#1720) * [removed-rule] Removed `no-unreachable` rule; covered by compiler (#661) @@ -52,6 +102,7 @@ Thanks to our contributors! v4.0.0-dev.1 --- + * **BREAKING CHANGES** * [enhancement] The `semicolon` rule now disallows semicolons in multi-line bound class methods (to get the v3 behavior, use the `ignore-bound-class-methods` option) (#1643) @@ -92,6 +143,7 @@ Thanks to our contributors! v4.0.0-dev.0 --- + * **BREAKING CHANGES** * [enhancement] Drop support for configuration via package.json (#1579) * [removed-rule] Removed `no-duplicate-key` rule; covered by compiler (#1109) @@ -136,14 +188,17 @@ Thanks to our contributors! v3.15.1 --- + * Enabled additional rules in `tslint:latest` configuration (#1506) v3.15.0 --- + * Stable release containing changes from the last dev release (v3.15.0-dev.0) v3.15.0-dev.0 --- + * [enhancement] Rules can automatically fix errors (#1423) * [enhancement] Better error messages for invalid source files (#1480) * [new-rule] `adjacent-overload-signatures` rule (#1426) @@ -170,10 +225,12 @@ Thanks to our contributors! v3.14.0 --- + * Stable release containing changes from the last dev releases (v3.14.0-dev.0, v3.14.0-dev.1) v3.14.0-dev.1 --- + * [new-rule] `arrow-parens` rule (#777) * [new-rule] `max-file-line-count` rule (#1360) * [new-rule] `no-unsafe-finally` rule (#1349) @@ -192,6 +249,7 @@ Thanks to our contributors! v3.14.0-dev.0 --- + * [enhancement] Add optional type information to rules (#1323) Thanks to our contributors! @@ -199,10 +257,12 @@ Thanks to our contributors! v3.13.0 --- + * Stable release containing changes from the last dev release (v3.13.0-dev.0) v3.13.0-dev.0 --- + * [new-rule] `ordered-imports` rule (#1325) * [enhancement] MPEG transport stream files are ignored by the CLI (#1357) @@ -215,22 +275,27 @@ Thanks to our contributors! v3.12.0-dev.2 --- + * [enhancement] Support TypeScript v2.0.0-dev builds v3.12.1 --- + * Stable release containing changes from the last dev release (v3.12.0-dev.1) v3.12.0-dev.1 --- + * [bugfix] Fix null reference bug in typedef rule (#1345) v3.12.0 --- + * Stable release containing changes from the last dev release (v3.12.0-dev.0) v3.12.0-dev.0 --- + * [new-rule] `only-arrow-functions` rule (#1318) * [new-rule] `no-unused-new` rule (#1316) * [new-rule-option] `arrow-call-signature` option for `typedef` rule (#1284) @@ -254,10 +319,12 @@ Thanks to our contributors! v3.11.0 --- + * Stable release containing changes from the last dev release (v3.11.0-dev.0) v3.11.0-dev.0 --- + * [new-rule] `linebreak-style` rule (#123) * [new-rule] `no-mergeable-namespace` rule (#843) * [enhancement] Add built-in configurations (#1261) @@ -276,10 +343,12 @@ Thanks to our contributors! v3.10.2 --- + * Stable release containing changes from the last dev release (v3.10.0-dev.2) v3.10.0-dev.2 --- + * [bugfix] `member-ordering` rule doesn't crash on methods in class expressions (#1252) * [bugfix] `ban` rule handles chained methods appropriately (#1234) @@ -288,18 +357,22 @@ Thanks to our contributors! v3.10.1 --- + * Stable release containing changes from the last dev release (v3.10.0-dev.1) v3.10.0-dev.1 --- + * [bugfix] `member-ordering` rule doesn't crash on methods in object literals (#1243) v3.10.0 --- + * Stable release containing changes from the last dev release (v3.10.0-dev.0) v3.10.0-dev.0 --- + * [new-rule] `new-parens` rule (#1177) * [new-rule] `no-default-export` rule (#1182) * [new-rule-option] `order: ...` option for `member-ordering` rule (#1208) @@ -316,10 +389,12 @@ Thanks to our contributors! v3.9.0 --- + * Stable release containing changes from the last dev release (v3.9.0-dev.0) v3.9.0-dev.0 --- + * [new-rule] `no-namespace` rule (#1133) * [new-rule] `one-variable-per-declaration` rule (#525) * [new-rule-option] "ignore-params" option for `no-inferrable-types` rule (#1190) @@ -344,19 +419,23 @@ Thanks to our contributors! v3.8.1 --- + * Stable release containing changes from the last dev release (v3.8.0-dev.1) v3.8.0-dev.1 --- + * [bugfix] Allow JS directives at the start of constructors, getters, and setters (#1159) * [bugfix] Remove accidentally included performance profiles from published NPM artifact (#1160) v3.8.0 --- + * Stable release containing changes from the last dev release (v3.8.0-dev.0) v3.8.0-dev.0 --- + * [new-rule] `no-invalid-this` rule (#1105) * [new-rule] `use-isnan` rule (#1054) * [new-rule] `no-reference` rule (#1139) @@ -380,26 +459,32 @@ Thanks to our contributors! v3.7.4 --- + * Stable release containing changes from the last dev release (v3.7.0-dev.5) v3.7.0-dev.5 --- + * [bugfix] Allow JS directives in namespaces (#1115) v3.7.3 --- + * Stable release containing changes from the last dev release (v3.7.0-dev.4) v3.7.0-dev.4 --- + * [bugfix] Downgrade `findup-sync` dependency (#1108) v3.7.2 --- + * Stable release containing changes from the last dev release (v3.7.0-dev.3) v3.7.0-dev.3 --- + * [bugfix] `findConfigurationPath` always returns an absolute path (#1093) * [bugfix] Update `findup-sync` dependency (#1080) * [bugfix] `declare global` no longer triggers `no-internal-module` rule (#1069) @@ -411,14 +496,17 @@ v3.7.1 v3.7.0-dev.2 --- + * [bugfix] Improve handling of paths provided via the -c CLI option (#1083) v3.7.0 --- + * Stable release containing changes from the last dev release v3.7.0-dev.1 --- + * [enhancement] `extends` field for `tslint.json` files (#997) * [enhancement] `--force` CLI option (#1059) * [enhancement] Improve how `Linter` class handles configurations with a `rulesDirectory` field (#1035) @@ -438,10 +526,12 @@ Thanks to our contributors! v3.6.0 --- + * Stable release containing changes from the last dev release v3.6.0-dev.1 --- + * [enhancement] Add `--exclude` CLI option (#915) * [bugfix] Fix `no-shadowed-variable` rule handling of standalone blocks (#1021) * [deprecation] Configuration through `package.json` files (#1020) @@ -454,10 +544,12 @@ Thanks to our contributors! v3.5.0 --- + * Stable release containing changes from the last dev release v3.5.0-dev.1 --- + * [new-rule-option] "ignore-pattern" option for `no-unused-variable` rule (#314) * [bugfix] Fix occassional crash in `no-string-literal` rule (#906) * [enhancement] Tweak behavior of `member-ordering` rule with regards to arrow function types in interfaces (#226) @@ -468,10 +560,12 @@ Thanks to our contributors! v3.4.0 --- + * Stable release containing changes from the last two dev releases v3.4.0-dev.2 --- + * [new-rule-option] "arrow-parameter" option for `typedef` rule (#333) * [new-rule-option] "never" option for `semicolon` rule (#363) * [new-rule-option] "onespace" setting for `typedef-whitespace` rule (#888) @@ -489,6 +583,7 @@ Thanks to our contributors! v3.4.0-dev.1 --- + * [enhancement] Revamped testing system (#620) * Writing tests for rules is now much simpler with a linter DSL. See exisitng tests in `test/rules/**/*.ts.lint` for examples. @@ -506,18 +601,22 @@ Thanks to our contributors! v3.3.0 --- + * [bugfix] Tweak TSLint build so TSLint works with typescript@next (#926) v3.3.0-dev.1 --- + * [bugfix] Correctly handle more than one custom rules directory (#928) v3.2.2 --- + * Stable release containing changes from the last dev release v3.2.2-dev.1 --- + * [enhancement] Throw an error if a path to a directory of custom rules is invalid (#910) * [new-rule-option] "jsx-single" and "jsx-double" options for `quotemark` rule (#673) * [bugfix] Handle paths to directories of custom rules more accurately @@ -525,43 +624,52 @@ v3.2.2-dev.1 v3.2.1 --- + * Stable release containing changes from the last dev release v3.2.1-dev.1 --- + * [enhancement] automatically generate a `tslint.json` file with new `--init` CLI command (#717) * [bugfix] `no-var-keyword` rule detects the use of `var` in all types of `for` loops (#855) v3.2.0 --- + * Stable release containing changes from last two dev releases v3.2.0-dev.2 --- + * [bugfix] formatters are now exported correctly to work with TS 1.8 (#863) v3.2.0-dev.1 --- + * [bugfix] fixed bug in how custom rules directories are registered (#844) * [enhancement] better support for globs in CLI (#827) * [new-rule] `no-null-keyword` rule (#722) v3.1.1 --- + * Bump TypeScript peer dependency to `>= 1.7.3` due to `const enum` incompatibility (#832) v3.1.0 --- + * [bugfix] build with TS v1.7.3 to fix null pointer exception (#832) * [bugfix] fixed false positive in `no-require-imports` rule (#816) v3.1.0-dev.1 --- + * [bugfix] fixed `no-shadowed-variable` false positives when handling destructuring in function params (#727) * [enhancement] `rulesDirectory` in `tslint.json` now supports multiple file paths (#795) v3.0.0 --- + * [bugfix] `member-access` rule now handles object literals and get/set accessors properly (#801) * New rule options: `check-accessor` and `check-constructor` * All the changes from the following releases, including some **breaking changes**: @@ -573,18 +681,21 @@ v3.0.0 v3.0.0-dev.3 --- + * TypeScript is now a peerDependency (#791) * [bugfix] `no-unused-variable` rule with `react` option works with self-closing JSX tags (#776) * [bugfix] `use-strict` bugfix (#544) v3.0.0-dev.2 --- + * [new-rule-option] "react" option for `no-unused-variable` rule (#698, #725) * [bugfix] Fix how `Linter` is exported from "tslint" module (#760) * [bugfix] `no-use-before-declare` rule doesn't crash on uncompilable code (#763) v3.0.0-dev.1 --- + * **BREAKING CHANGES** * Rearchitect TSLint to use external modules instead of merged namespaces (#726) * Dependencies need to be handled differently now by custom rules and formatters @@ -599,15 +710,18 @@ v3.0.0-dev.1 v2.6.0-dev.2 --- + * Upgrade TypeScript compiler to `v1.7.0-dev.20151003` * [bugfix] `no-unused-expression` rule now handles yield expressions properly (#706) v2.6.0-dev.1 --- + * Upgrade TypeScript compiler to `v1.7.0-dev.20150924` v2.5.1 --- + * [new-rule] no-inferrable-types rule (#676) * [new-rule-option] "avoid-escape" option for quotemark rule (#543) * [bugfix] type declaration for tslint external module #686 @@ -616,6 +730,7 @@ v2.5.1 v2.5.0 --- + * Use TypeScript compiler `v1.6.2` * [bugfixes] #637, #642, #650, #652 * [bugfixes] fix various false positives in `no-unused-variable` rule (#570, #613, #663) @@ -623,44 +738,53 @@ v2.5.0 v2.5.0-beta --- + * Use TypeScript compiler `v1.6.0-beta` * [bugfix] Fix `no-internal-module` false positives on nested namespaces (#600) * [docs] Add documentation for `sort-object-literal-keys` rule v2.5.0-dev.5 --- + * Upgrade TypeScript compiler to `v1.7.0-dev.20150828` * [bugfix] Handle .tsx files appropriately (#597, #558) v2.5.0-dev.4 --- + * Upgrade TypeScript compiler to `v1.6.0-dev.20150825` v2.5.0-dev.3 --- + * Upgrade TypeScript compiler to `v1.6.0-dev.20150821` v2.5.0-dev.2 --- + * Upgrade TypeScript compiler to `v1.6.0-dev.20150811` * [bug] fix `whitespace` false positive in JSX elements (#559) v2.5.0-dev.1 --- + * Upgrade TypeScript compiler to `v1.6.0-dev.20150805` * [enhancement] Support `.tsx` syntax (#490) v2.4.5 --- + * [bugfix] fix false positives on `no-shadowed-variable` rule (#500) * [enhancement] add `allow-trailing-underscore` option to `variable-name` rule v2.4.4 --- + * [bugfix] remove "typescript" block from package.json (#606) v2.4.3 --- + * [new-rule] `no-conditional-assignment` (#507) * [new-rule] `member-access` (#552) * [new-rule] `no-internal-module` (#513) @@ -673,10 +797,12 @@ v2.4.3 v2.4.2 --- + * [bug] remove npm-shrinkwrap.json from the published package v2.4.0 --- + * Upgraded Typescript compiler to 1.5.3 * [bugs] #332, #493, #509, #483 * [bug] fix error message in `no-var-keyword` rule @@ -686,6 +812,7 @@ v2.4.0 v2.3.1-beta --- + * [bugs] #137 #434 #451 #456 * [new-rule] `no-require-imports` disallows `require()` style imports * [new-rule] `no-shadowed-variable` moves over shadowed variable checking from `no-duplicate-variable` into its own rule @@ -696,6 +823,7 @@ v2.3.1-beta v2.3.0-beta --- + * [bugs] #401 #367 #324 #352 * [new-rule] `no-var-keyword` disallows `var` in favor of `let` and `const` * [new-rule] `sort-object-literal-keys` forces object-literal keys to be sorted alphabetically @@ -705,6 +833,7 @@ v2.3.0-beta v2.2.0-beta --- + * Upgraded Typescript compiler to 1.5.0-beta * **BREAKING CHANGES** * due to changes to the typescript compiler API, old custom rules may no longer work and may need to be rewritten @@ -718,16 +847,19 @@ v2.2.0-beta v2.1.1 --- + * [bugs] #292 #293 #295 #301 #302 * Some internal refactoring * Added Windows CI testing (appveyor) v2.1.0 --- + * Fix crash on Windows v2.0.1 --- + * Upgraded Typescript compiler to 1.4 * **BREAKING CHANGES** * typedef rule options were modified: @@ -743,14 +875,17 @@ v2.0.1 v1.2.0 --- + * [bug] #245 v1.0.1 --- + * [bug] #238 v1.0.0 --- + * upgrade TypeScript compiler to 1.3 * **BREAKING CHANGES** * all error messages now start with a lower-case character and do not end with a period @@ -760,26 +895,31 @@ v1.0.0 v0.4.12 --- + * multiple files with -f on cli * config file search starts with input file v0.4.11 --- + * [bugs] #136, #163 * internal refactors v0.4.10 --- + * [bugs] #138, #145, #146, #148 v0.4.9 --- + * [new-rule] `no-any` disallows all uses of `any` * [bug] `/* tslint:disable */` now disables semicolon rule as well * [bug] delete operator no longer results in a false positive for `no-unused-expression` v0.4.8 --- + * [new-rule] `no-var-requires` disallows require statements not part of an import statement * [new-rule] `typedef` rule also checks for member variables * [bug] `no-unused-variable` no longer triggers false positives for class members labeled only `static` @@ -789,6 +929,7 @@ v0.4.8 v0.4.7 --- + * [new-rule] added `no-unused-expression` rule which disallows unused expression statements * [feature] the `check-operator` option for the `whitespace` rule now checks whitespace around the => token * [bug] `no-use-before-declare-rule` no longer triggers false positives for member variables of classes used before the class is declared @@ -801,6 +942,7 @@ v0.4.7 v0.4.6 --- + * [build] migrated build to use `grunt-ts` instead of `grunt-typescript` * [feature] `package.json` now contains a `tslintConfig` paramater to allow users to specify the location of the configuration file there * [feature] tslint now searches for the configuration file in the user's home directory if not found in the current path @@ -808,16 +950,19 @@ v0.4.6 v0.4.5 --- + * [feature] `no-unused-variable` no longer checks parameters by defualt. Parameters are now only checked if the `check-parameters` option is set. * [bug] `no-unused-variable` parameter check no longer fails on variable argument parameters (like ...args) and on cases where the parameters are broken up by newlines. v0.4.4 --- + * [bug] `no-unused-variable` validates function parameters and constructor methods * [bug] `no-empty` and `no-trailing-comma` rules handle empty objects v0.4.3 --- + * [new-rule] `no-unused-variable` * [new-rule] `no-trailing-comma` * [new-rule] `no-use-before-declare` diff --git a/docs/_data/rules.json b/docs/_data/rules.json index e6ae7e1e2c1..b7320f8c250 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -70,7 +70,7 @@ { "ruleName": "ban", "description": "Bans the use of specific functions or global methods.", - "optionsDescription": "\nA list of `['object', 'method', 'optional explanation here']` or `['globalMethod']` which ban `object.method()` \nor respectively `globalMethod()`.", + "optionsDescription": "\nA list of `['object', 'method', 'optional explanation here']` or `['globalMethod']` which ban `object.method()`\nor respectively `globalMethod()`.", "options": { "type": "list", "listType": { @@ -83,7 +83,7 @@ } }, "optionExamples": [ - "[true, [\"someGlobalMethod\"], [\"someObject\", \"someFunction\"], \n [\"someObject\", \"otherFunction\", \"Optional explanation\"]]" + "[true, [\"someGlobalMethod\"], [\"someObject\", \"someFunction\"],\n [\"someObject\", \"otherFunction\", \"Optional explanation\"]]" ], "type": "functionality", "typescriptOnly": false @@ -142,7 +142,7 @@ }, "optionExamples": [ "true", - "[true, [\"classes\", \"functions\"]" + "[true, \"classes\", \"functions\"]" ], "type": "style", "typescriptOnly": false @@ -975,7 +975,7 @@ "ruleName": "object-literal-key-quotes", "description": "Enforces consistent object literal property quote style.", "descriptionDetails": "\nObject literal property names can be defined in two ways: using literals or using strings.\nFor example, these two objects are equivalent:\n\nvar object1 = {\n property: true\n};\n\nvar object2 = {\n \"property\": true\n};\n\nIn many cases, it doesn’t matter if you choose to use an identifier instead of a string\nor vice-versa. Even so, you might decide to enforce a consistent style in your code.\n\nThis rules lets you enforce consistent quoting of property names. Either they should always\nbe quoted (default behavior) or quoted only as needed (\"as-needed\").", - "optionsDescription": "\nPossible settings are:\n\n* `\"always\"`: Property names should always be quoted. (This is the default.)\n* `\"as-needed\"`: Only property names which require quotes may be quoted (e.g. those with spaces in them).\n* `\"consistent\"`: Property names should either all be quoted or unquoted.\n* `\"consistent-as-needed\"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no \nproperty names may be quoted.\n\nFor ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need\nto be quoted.", + "optionsDescription": "\nPossible settings are:\n\n* `\"always\"`: Property names should always be quoted. (This is the default.)\n* `\"as-needed\"`: Only property names which require quotes may be quoted (e.g. those with spaces in them).\n* `\"consistent\"`: Property names should either all be quoted or unquoted.\n* `\"consistent-as-needed\"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no\nproperty names may be quoted.\n\nFor ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need\nto be quoted.", "options": { "type": "string", "enum": [ @@ -1066,13 +1066,14 @@ "ruleName": "only-arrow-functions", "description": "Disallows traditional (non-arrow) function expressions.", "rationale": "Traditional functions don't bind lexical scope, which can lead to unexpected behavior when accessing 'this'.", - "optionsDescription": "\nOne argument may be optionally provided:\n\n* `\"allow-declarations\"` allows standalone function declarations.\n ", + "optionsDescription": "\nTwo arguments may be optionally provided:\n\n* `\"allow-declarations\"` allows standalone function declarations.\n* `\"allow-named-functions\"` allows the expression `function foo() {}` but not `function() {}`.\n ", "options": { "type": "array", "items": { "type": "string", "enum": [ - "allow-declarations" + "allow-declarations", + "allow-named-functions" ] }, "minLength": 0, @@ -1080,7 +1081,7 @@ }, "optionExamples": [ "true", - "[true, \"allow-declarations\"]" + "[true, \"allow-declarations\", \"allow-named-functions\"]" ], "type": "typescript", "typescriptOnly": false @@ -1089,7 +1090,7 @@ "ruleName": "ordered-imports", "description": "Requires that import statements be alphabetized.", "descriptionDetails": "\nEnforce a consistent ordering for ES6 imports:\n- Named imports must be alphabetized (i.e. \"import {A, B, C} from \"foo\";\")\n - The exact ordering can be controlled by the named-imports-order option.\n - \"longName as name\" imports are ordered by \"longName\".\n- Import sources must be alphabetized within groups, i.e.:\n import * as foo from \"a\";\n import * as bar from \"b\";\n- Groups of imports are delineated by blank lines. You can use these to group imports\n however you like, e.g. by first- vs. third-party or thematically.", - "optionsDescription": "\nYou may set the `\"import-sources-order\"` option to control the ordering of source\nimports (the `\"foo\"` in `import {A, B, C} from \"foo\"`).\n\nPossible values for `\"import-sources-order\"` are:\n* `\"case-insensitive'`: Correct order is `\"Bar\"`, `\"baz\"`, `\"Foo\"`. (This is the default.)\n* `\"lowercase-first\"`: Correct order is `\"baz\"`, `\"Bar\"`, `\"Foo\"`.\n* `\"lowercase-last\"`: Correct order is `\"Bar\"`, `\"Foo\"`, `\"baz\"`.\n* `\"any\"`: Allow any order.\n\nYou may set the `\"named-imports-order\"` option to control the ordering of named\nimports (the `{A, B, C}` in `import {A, B, C} from \"foo\"`).\n\nPossible values for `\"named-imports-order\"` are:\n\n* `\"case-insensitive'`: Correct order is `{A, b, C}`. (This is the default.)\n* `\"lowercase-first\"`: Correct order is `{b, A, C}`.\n* `\"lowercase-last\"`: Correct order is `{A, C, b}`.\n* `\"any\"`: Allow any order.\n\n ", + "optionsDescription": "\nYou may set the `\"import-sources-order\"` option to control the ordering of source\nimports (the `\"foo\"` in `import {A, B, C} from \"foo\"`).\n\nPossible values for `\"import-sources-order\"` are:\n\n* `\"case-insensitive'`: Correct order is `\"Bar\"`, `\"baz\"`, `\"Foo\"`. (This is the default.)\n* `\"lowercase-first\"`: Correct order is `\"baz\"`, `\"Bar\"`, `\"Foo\"`.\n* `\"lowercase-last\"`: Correct order is `\"Bar\"`, `\"Foo\"`, `\"baz\"`.\n* `\"any\"`: Allow any order.\n\nYou may set the `\"named-imports-order\"` option to control the ordering of named\nimports (the `{A, B, C}` in `import {A, B, C} from \"foo\"`).\n\nPossible values for `\"named-imports-order\"` are:\n\n* `\"case-insensitive'`: Correct order is `{A, b, C}`. (This is the default.)\n* `\"lowercase-first\"`: Correct order is `{b, A, C}`.\n* `\"lowercase-last\"`: Correct order is `{A, C, b}`.\n* `\"any\"`: Allow any order.\n\n ", "options": { "type": "object", "properties": { @@ -1187,7 +1188,7 @@ { "ruleName": "radix", "description": "Requires the radix parameter to be specified when calling `parseInt`.", - "rationale": "\nFrom [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt): \n> Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior.\n> Different implementations produce different results when a radix is not specified, usually defaulting the value to 10.", + "rationale": "\nFrom [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt):\n> Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior.\n> Different implementations produce different results when a radix is not specified, usually defaulting the value to 10.", "optionsDescription": "Not configurable.", "options": null, "optionExamples": [ diff --git a/docs/rules/ban/index.html b/docs/rules/ban/index.html index f3005766579..8d301eea4dc 100644 --- a/docs/rules/ban/index.html +++ b/docs/rules/ban/index.html @@ -3,7 +3,7 @@ description: Bans the use of specific functions or global methods. optionsDescription: |- - A list of `['object', 'method', 'optional explanation here']` or `['globalMethod']` which ban `object.method()` + A list of `['object', 'method', 'optional explanation here']` or `['globalMethod']` which ban `object.method()` or respectively `globalMethod()`. options: type: list @@ -15,7 +15,7 @@ maxLength: 3 optionExamples: - |- - [true, ["someGlobalMethod"], ["someObject", "someFunction"], + [true, ["someGlobalMethod"], ["someObject", "someFunction"], ["someObject", "otherFunction", "Optional explanation"]] type: functionality typescriptOnly: false diff --git a/docs/rules/completed-docs/index.html b/docs/rules/completed-docs/index.html index 128a663d3db..0ea545d71e5 100644 --- a/docs/rules/completed-docs/index.html +++ b/docs/rules/completed-docs/index.html @@ -17,7 +17,7 @@ - properties optionExamples: - 'true' - - '[true, ["classes", "functions"]' + - '[true, "classes", "functions"]' type: style typescriptOnly: false layout: rule diff --git a/docs/rules/object-literal-key-quotes/index.html b/docs/rules/object-literal-key-quotes/index.html index 66b593d1860..0f2fa437a5c 100644 --- a/docs/rules/object-literal-key-quotes/index.html +++ b/docs/rules/object-literal-key-quotes/index.html @@ -26,7 +26,7 @@ * `"always"`: Property names should always be quoted. (This is the default.) * `"as-needed"`: Only property names which require quotes may be quoted (e.g. those with spaces in them). * `"consistent"`: Property names should either all be quoted or unquoted. - * `"consistent-as-needed"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no + * `"consistent-as-needed"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no property names may be quoted. For ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need diff --git a/docs/rules/only-arrow-functions/index.html b/docs/rules/only-arrow-functions/index.html index f88c10f5bbb..bff41cac593 100644 --- a/docs/rules/only-arrow-functions/index.html +++ b/docs/rules/only-arrow-functions/index.html @@ -4,9 +4,10 @@ rationale: 'Traditional functions don''t bind lexical scope, which can lead to unexpected behavior when accessing ''this''.' optionsDescription: |- - One argument may be optionally provided: + Two arguments may be optionally provided: * `"allow-declarations"` allows standalone function declarations. + * `"allow-named-functions"` allows the expression `function foo() {}` but not `function() {}`. options: type: array @@ -14,11 +15,12 @@ type: string enum: - allow-declarations + - allow-named-functions minLength: 0 maxLength: 1 optionExamples: - 'true' - - '[true, "allow-declarations"]' + - '[true, "allow-declarations", "allow-named-functions"]' type: typescript typescriptOnly: false layout: rule @@ -29,7 +31,8 @@ "items": { "type": "string", "enum": [ - "allow-declarations" + "allow-declarations", + "allow-named-functions" ] }, "minLength": 0, diff --git a/docs/rules/ordered-imports/index.html b/docs/rules/ordered-imports/index.html index 78d7a0a5466..b05f21b3a46 100644 --- a/docs/rules/ordered-imports/index.html +++ b/docs/rules/ordered-imports/index.html @@ -18,6 +18,7 @@ imports (the `"foo"` in `import {A, B, C} from "foo"`). Possible values for `"import-sources-order"` are: + * `"case-insensitive'`: Correct order is `"Bar"`, `"baz"`, `"Foo"`. (This is the default.) * `"lowercase-first"`: Correct order is `"baz"`, `"Bar"`, `"Foo"`. * `"lowercase-last"`: Correct order is `"Bar"`, `"Foo"`, `"baz"`. diff --git a/docs/rules/radix/index.html b/docs/rules/radix/index.html index f951cffb6ac..b3af9ac6b9d 100644 --- a/docs/rules/radix/index.html +++ b/docs/rules/radix/index.html @@ -3,7 +3,7 @@ description: Requires the radix parameter to be specified when calling `parseInt`. rationale: |- - From [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt): + From [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt): > Always specify this parameter to eliminate reader confusion and to guarantee predictable behavior. > Different implementations produce different results when a radix is not specified, usually defaulting the value to 10. optionsDescription: Not configurable. diff --git a/package.json b/package.json index 047ac1dd9c2..a81d870b344 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "4.0.2", + "version": "4.1.0", "description": "An extensible static analysis linter for the TypeScript language", "bin": { "tslint": "./bin/tslint" diff --git a/src/linter.ts b/src/linter.ts index 45d576bdbe2..1e27d8193ce 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -42,7 +42,7 @@ import { arrayify, dedent } from "./utils"; * Linter that can lint multiple files in consecutive runs. */ class Linter { - public static VERSION = "4.0.2"; + public static VERSION = "4.1.0"; public static findConfiguration = findConfiguration; public static findConfigurationPath = findConfigurationPath; From 0b150299209851251797f870d4366eb88a1cff8b Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 16 Dec 2016 11:44:27 -0500 Subject: [PATCH 43/48] Fix false positive in `typedef` rule for catch clause (#1887) --- src/rules/typedefRule.ts | 2 +- test/rules/typedef/all/test.ts.lint | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index 876f7633bab..2587f73840f 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -185,7 +185,7 @@ class TypedefWalker extends Lint.RuleWalker { // catch statements will be the parent of the variable declaration // for-in/for-of loops will be the gradparent of the variable declaration if (node.parent != null && node.parent.parent != null - && node.parent.parent.kind !== ts.SyntaxKind.CatchClause + && (node as ts.Node).parent.kind !== ts.SyntaxKind.CatchClause && node.parent.parent.kind !== ts.SyntaxKind.ForInStatement && node.parent.parent.kind !== ts.SyntaxKind.ForOfStatement) { this.checkTypeAnnotation("variable-declaration", node.name.getEnd(), node.type, node.name); diff --git a/test/rules/typedef/all/test.ts.lint b/test/rules/typedef/all/test.ts.lint index 3e026023f74..b2991703b9c 100644 --- a/test/rules/typedef/all/test.ts.lint +++ b/test/rules/typedef/all/test.ts.lint @@ -1,3 +1,8 @@ +// should not error with missing exception type +try { +} catch (e) { +} + var NoTypeObjectLiteralWithPropertyGetter = { ~ [expected variable-declaration: 'NoTypeObjectLiteralWithPropertyGetter' to have a typedef] Prop: "some property", From c55ac4870907b17ca8cc80b9cae27927439040b4 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 16 Dec 2016 08:46:23 -0800 Subject: [PATCH 44/48] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3eed246d80..a206996bebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Change Log === +v4.1.1 +--- + +* [bugfix] `typedef` rule was showing false positive for `catch` clause (#1887) + v4.1.0 --- From 124ae1f332c3d419554fb80e8a299b5feb555db4 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 16 Dec 2016 11:49:39 -0500 Subject: [PATCH 45/48] Prepare v4.1.1 (#1888) --- package.json | 2 +- src/linter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a81d870b344..466a570db59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "4.1.0", + "version": "4.1.1", "description": "An extensible static analysis linter for the TypeScript language", "bin": { "tslint": "./bin/tslint" diff --git a/src/linter.ts b/src/linter.ts index 1e27d8193ce..14e86e2daad 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -42,7 +42,7 @@ import { arrayify, dedent } from "./utils"; * Linter that can lint multiple files in consecutive runs. */ class Linter { - public static VERSION = "4.1.0"; + public static VERSION = "4.1.1"; public static findConfiguration = findConfiguration; public static findConfigurationPath = findConfigurationPath; From 9d29586e99463d5a0a3bdc96ebfda46c0c17a650 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 16 Dec 2016 09:30:31 -0800 Subject: [PATCH 46/48] Fix `cyclomatic-complexity` doc permanent fix for #1808 --- src/rules/cyclomaticComplexityRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/cyclomaticComplexityRule.ts b/src/rules/cyclomaticComplexityRule.ts index 8cc21f54960..251ebc7f7b4 100644 --- a/src/rules/cyclomaticComplexityRule.ts +++ b/src/rules/cyclomaticComplexityRule.ts @@ -28,7 +28,7 @@ export class Rule extends Lint.Rules.AbstractRule { ruleName: "cyclomatic-complexity", description: "Enforces a threshold of cyclomatic complexity.", descriptionDetails: Lint.Utils.dedent` - Cyclomatic complexity is assessed for each function of any type. A starting value of 1 + Cyclomatic complexity is assessed for each function of any type. A starting value of 20 is assigned and this value is then incremented for every statement which can branch the control flow within the function. The following statements and expressions contribute to cyclomatic complexity: From 4ab7691cd69fe896990fed1879fa39abc20a7939 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 16 Dec 2016 10:30:11 -0800 Subject: [PATCH 47/48] Add no-empty-interface rule (#1889) --- docs/_data/rules.json | 14 +++++- docs/rules/cyclomatic-complexity/index.html | 2 +- docs/rules/no-empty-interface/index.html | 14 ++++++ src/rules/noEmptyInterfaceRule.ts | 51 +++++++++++++++++++++ test/rules/no-empty-interface/test.ts.lint | 7 +++ test/rules/no-empty-interface/tslint.json | 5 ++ 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 docs/rules/no-empty-interface/index.html create mode 100644 src/rules/noEmptyInterfaceRule.ts create mode 100644 test/rules/no-empty-interface/test.ts.lint create mode 100644 test/rules/no-empty-interface/tslint.json diff --git a/docs/_data/rules.json b/docs/_data/rules.json index b7320f8c250..c5b67f588ce 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -162,7 +162,7 @@ { "ruleName": "cyclomatic-complexity", "description": "Enforces a threshold of cyclomatic complexity.", - "descriptionDetails": "\nCyclomatic complexity is assessed for each function of any type. A starting value of 1\nis assigned and this value is then incremented for every statement which can branch the\ncontrol flow within the function. The following statements and expressions contribute\nto cyclomatic complexity:\n* `catch`\n* `if` and `? :`\n* `||` and `&&` due to short-circuit evaluation\n* `for`, `for in` and `for of` loops\n* `while` and `do while` loops", + "descriptionDetails": "\nCyclomatic complexity is assessed for each function of any type. A starting value of 20\nis assigned and this value is then incremented for every statement which can branch the\ncontrol flow within the function. The following statements and expressions contribute\nto cyclomatic complexity:\n* `catch`\n* `if` and `? :`\n* `||` and `&&` due to short-circuit evaluation\n* `for`, `for in` and `for of` loops\n* `while` and `do while` loops", "rationale": "\nCyclomatic complexity is a code metric which indicates the level of complexity in a\nfunction. High cyclomatic complexity indicates confusing code which may be prone to\nerrors or difficult to modify.", "optionsDescription": "\nAn optional upper limit for cyclomatic complexity can be specified. If no limit option\nis provided a default value of $(Rule.DEFAULT_THRESHOLD) will be used.", "options": { @@ -600,6 +600,18 @@ "type": "functionality", "typescriptOnly": false }, + { + "ruleName": "no-empty-interface", + "description": "Forbids empty interfaces.", + "rationale": "An empty interface is equivalent to its supertype (or `{}`).", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "typescript", + "typescriptOnly": true + }, { "ruleName": "no-empty", "description": "Disallows empty blocks.", diff --git a/docs/rules/cyclomatic-complexity/index.html b/docs/rules/cyclomatic-complexity/index.html index 4eddc29446d..12ba0162133 100644 --- a/docs/rules/cyclomatic-complexity/index.html +++ b/docs/rules/cyclomatic-complexity/index.html @@ -3,7 +3,7 @@ description: Enforces a threshold of cyclomatic complexity. descriptionDetails: |- - Cyclomatic complexity is assessed for each function of any type. A starting value of 1 + Cyclomatic complexity is assessed for each function of any type. A starting value of 20 is assigned and this value is then incremented for every statement which can branch the control flow within the function. The following statements and expressions contribute to cyclomatic complexity: diff --git a/docs/rules/no-empty-interface/index.html b/docs/rules/no-empty-interface/index.html new file mode 100644 index 00000000000..a2858364866 --- /dev/null +++ b/docs/rules/no-empty-interface/index.html @@ -0,0 +1,14 @@ +--- +ruleName: no-empty-interface +description: Forbids empty interfaces. +rationale: 'An empty interface is equivalent to its supertype (or `{}`).' +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: no-empty-interface' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/src/rules/noEmptyInterfaceRule.ts b/src/rules/noEmptyInterfaceRule.ts new file mode 100644 index 00000000000..3c6911de923 --- /dev/null +++ b/src/rules/noEmptyInterfaceRule.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2013 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 * as Lint from "../index"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "no-empty-interface", + description: "Forbids empty interfaces.", + rationale: "An empty interface is equivalent to its supertype (or `{}`).", + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "typescript", + typescriptOnly: true, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "An empty interface is equivalent to `{}`."; + public static FAILURE_STRING_FOR_EXTENDS = "An interface declaring no members is equivalent to its supertype."; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends Lint.RuleWalker { + public visitInterfaceDeclaration(node: ts.InterfaceDeclaration) { + if (node.members.length === 0) { + this.addFailureAtNode(node.name, node.heritageClauses ? Rule.FAILURE_STRING_FOR_EXTENDS : Rule.FAILURE_STRING); + } + super.visitInterfaceDeclaration(node); + } +} diff --git a/test/rules/no-empty-interface/test.ts.lint b/test/rules/no-empty-interface/test.ts.lint new file mode 100644 index 00000000000..3fb5872674c --- /dev/null +++ b/test/rules/no-empty-interface/test.ts.lint @@ -0,0 +1,7 @@ +interface I { } + ~ [An empty interface is equivalent to `{}`.] + +interface J extends I { } + ~ [An interface declaring no members is equivalent to its supertype.] + +interface K { x: number; } diff --git a/test/rules/no-empty-interface/tslint.json b/test/rules/no-empty-interface/tslint.json new file mode 100644 index 00000000000..366f38b35e9 --- /dev/null +++ b/test/rules/no-empty-interface/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-empty-interface": true + } +} From e635955ad31e08917a11ab17c12a6c8b2e0320aa Mon Sep 17 00:00:00 2001 From: James Booth Date: Fri, 16 Dec 2016 19:59:31 +0000 Subject: [PATCH 48/48] Fix string quotes on docs rule array-type (#1882) --- docs/rules/array-type/index.html | 8 ++++---- src/rules/arrayTypeRule.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rules/array-type/index.html b/docs/rules/array-type/index.html index 5a5cfe27139..4595a595fd1 100644 --- a/docs/rules/array-type/index.html +++ b/docs/rules/array-type/index.html @@ -15,9 +15,9 @@ - generic - array-simple optionExamples: - - '[true, array]' - - '[true, generic]' - - '[true, array-simple]' + - '[true, "array"]' + - '[true, "generic"]' + - '[true, "array-simple"]' type: style typescriptOnly: true layout: rule @@ -31,4 +31,4 @@ "array-simple" ] } ---- \ No newline at end of file +--- diff --git a/src/rules/arrayTypeRule.ts b/src/rules/arrayTypeRule.ts index cf58e16a69f..8e070121998 100644 --- a/src/rules/arrayTypeRule.ts +++ b/src/rules/arrayTypeRule.ts @@ -21,7 +21,7 @@ export class Rule extends Lint.Rules.AbstractRule { type: "string", enum: [OPTION_ARRAY, OPTION_GENERIC, OPTION_ARRAY_SIMPLE], }, - optionExamples: [`[true, ${OPTION_ARRAY}]`, `[true, ${OPTION_GENERIC}]`, `[true, ${OPTION_ARRAY_SIMPLE}]`], + optionExamples: [`[true, "${OPTION_ARRAY}"]`, `[true, "${OPTION_GENERIC}"]`, `[true, "${OPTION_ARRAY_SIMPLE}"]`], type: "style", typescriptOnly: true, };