diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ff0dce1f6..83bf2d6dca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ Change Log === +v4.0.0-dev.3 +--- +* Include latest v4.0.2 changes + +v4.0.2 +--- +* [enhancement] Don't exit when a rule can't be found. Print as a warning instead (#1771) +* [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) + +Thanks to our contributors! +* @gustavderdrache + +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 diff --git a/README.md b/README.md index 01c8c313af2..662d9c150e9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ TSLint supports: - custom formatters (failure reporters) - inline disabling / enabling of rules - configuration presets (`tslint:latest`, `tslint-react`, etc.) & composition +- automatic fixing of formatting & style violations - integration with [msbuild](https://github.com/joshuakgoldberg/tslint.msbuild), [grunt](https://github.com/palantir/grunt-tslint), [gulp](https://github.com/panuhorsmalahti/gulp-tslint), [atom](https://github.com/AtomLinter/linter-tslint), [eclipse](https://github.com/palantir/eclipse-tslint), [emacs](http://flycheck.org), [sublime](https://packagecontrol.io/packages/SublimeLinter-contrib-tslint), [vim](https://github.com/scrooloose/syntastic), [visual studio](https://visualstudiogallery.msdn.microsoft.com/6edc26d4-47d8-4987-82ee-7c820d79be1d), [vscode](https://marketplace.visualstudio.com/items?itemName=eg2.tslint), [webstorm](https://www.jetbrains.com/webstorm/help/tslint.html), and more Table of Contents @@ -115,7 +116,7 @@ Options: ``` -c, --config configuration file -e, --exclude exclude globs from path expansion ---fix Fixes linting errors for select rules. This may overwrite linted files +--fix fixes linting errors for select rules (this may overwrite linted files) --force return status code 0 even if there are lint errors -h, --help display detailed help -i, --init generate a tslint.json config file in the current working directory @@ -213,9 +214,9 @@ tslint accepts the following command-line options: #### Library -```javascript -const Linter = require("tslint"); -const fs = require("fs"); +```js +import { Linter } from "tslint"; +import * as fs from "fs"; const fileName = "Specify file name"; const configuration = { @@ -240,7 +241,7 @@ const result = linter.lint(); To enable rules that work with the type checker, a TypeScript program object must be passed to the linter when using the programmatic API. Helper functions are provided to create a program from a `tsconfig.json` file. A project directory can be specified if project files do not lie in the same directory as the `tsconfig.json` file. -```javascript +```js const program = Linter.createProgram("tsconfig.json", "projectDir/"); const files = Linter.getFileNames(program); const results = files.map(file => { @@ -321,20 +322,20 @@ __Important conventions__: Now, let us first write the rule in TypeScript: -```typescript +```ts import * as ts from "typescript"; -import * as Lint from "tslint"; +import { Rules, RuleFailure, RuleWalker } from "tslint"; -export class Rule extends Lint.Rules.AbstractRule { +export class Rule extends Rules.AbstractRule { public static FAILURE_STRING = "import statement forbidden"; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + public apply(sourceFile: ts.SourceFile): RuleFailure[] { return this.applyWithWalker(new NoImportsWalker(sourceFile, this.getOptions())); } } // The walker takes care of all the work. -class NoImportsWalker extends Lint.RuleWalker { +class NoImportsWalker extends RuleWalker { public visitImportDeclaration(node: ts.ImportDeclaration) { // create a failure at the current position this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); @@ -355,7 +356,7 @@ tsc --noImplicitAny noImportsRule.ts Then, if using the CLI, provide the directory that contains this rule as an option to `--rules-dir`. If using TSLint as a library or via `grunt-tslint`, the `options` hash must contain `"rulesDirectory": "..."`. If you run the linter, you'll see that we have now successfully banned all import statements via TSLint! -Finally, enable each custom rule in your [`tslint.json` config file][0] config file. +Finally, enable each custom rule in your [`tslint.json` config file](https://palantir.github.io/tslint/usage/tslint-json/) config file. Final notes: @@ -370,11 +371,11 @@ Just like rules, additional formatters can also be supplied to TSLint via `--for ```typescript import * as ts from "typescript"; -import * as Lint from "tslint/lib/lint"; +import { Formatters, RuleFailure } from "tslint"; -export class Formatter extends Lint.Formatters.AbstractFormatter { - public format(failures: Lint.RuleFailure[]): string { - var failuresJSON = failures.map((failure: Lint.RuleFailure) => failure.toJson()); +export class Formatter extends Formatters.AbstractFormatter { + public format(failures: RuleFailure[]): string { + var failuresJSON = failures.map((failure: RuleFailure) => failure.toJson()); return JSON.stringify(failuresJSON); } } @@ -412,5 +413,3 @@ Creating a new release 4. Commit with message `Prepare release ` 5. Run `npm publish` 6. Create a git tag for the new release and push it ([see existing tags here](https://github.com/palantir/tslint/tags)) - -[0]: {{site.baseurl | append: "/usage/tslint-json/"}} diff --git a/docs/_data/rules.json b/docs/_data/rules.json index 249191ad953..61d5c2d6c0b 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -296,7 +296,7 @@ }, { "ruleName": "max-classes-per-file", - "description": "\nA file may not contain more than the specified number of classes \nif the file name does not match the \"ignore-filename-pattern\" option", + "description": "\nA file may not contain more than the specified number of classes", "rationale": "\nEnsures that files have a single responsibility so that that classes each exist in their own files", "optionsDescription": "\nThe one required argument is an integer indicating the maximum number of classes that can appear in a file.", "options": { @@ -850,7 +850,7 @@ }, { "ruleName": "no-unused-variable", - "deprecationMessage": "Use the compiler options --noUnusedParameters and --noUnusedLocals instead.", + "deprecationMessage": "Use the tsc compiler options --noUnusedParameters and --noUnusedLocals instead.", "description": "Disallows unused imports, variables, functions and private class members.", "optionsDescription": "\nThree optional arguments may be optionally provided:\n\n* `\"check-parameters\"` disallows unused function and constructor parameters.\n * NOTE: this option is experimental and does not work with classes\n that use abstract method declarations, among other things.\n* `\"react\"` relaxes the rule for a namespace import named `React`\n(from either the module `\"react\"` or `\"react/addons\"`).\nAny JSX expression in the file will be treated as a usage of `React`\n(because it expands to `React.createElement `).\n* `{\"ignore-pattern\": \"pattern\"}` where pattern is a case-sensitive regexp.\nVariable names that match the pattern will be ignored.", "options": { diff --git a/docs/_posts/2016-11-17-new-for-4.0.md b/docs/_posts/2016-11-17-new-for-4.0.md index 29a1ca08595..2bbf2404c95 100644 --- a/docs/_posts/2016-11-17-new-for-4.0.md +++ b/docs/_posts/2016-11-17-new-for-4.0.md @@ -15,13 +15,15 @@ TSLint 4.0 has been released! With this release comes a few exciting [changes][0 * [semicolon][7] * [trailing-comma][8] -* **Linting `.js` files**. *A much-requested feature from our community*. Simplify your toolset by running the same rules you know and love on your .js and .jsx files. Just add a `jsRules` [section][9] to your `tslint.json` file, and TSLint will your JavaScript files. -* **TypeScript 2.0+ required**. This lets us deprecate/remove rules that are checked by the compiler. These rules now cause compilation errors: +* **Linting `.js` files**. *A much-requested feature from our community*. Simplify your toolset by running the same rules you know and love on your .js and .jsx files. Just add a `jsRules` [section][9] to your `tslint.json` file, and TSLint will lint your JavaScript files. + +* **TypeScript 2.0+ required**. This lets us deprecate/remove rules that are checked by the compiler. Problematic code that once violated these rules now cause compilation errors in `tsc`: * no-duplicate-key * no-unreachable * no-unused-variable + * **Node.js API Change**. [Moved and renamed][11] some things to make more sense. Get it all when you use `import * as TSLint from "tslint"`. * **[Recommended Rules Updated][12]** @@ -36,6 +38,7 @@ TSLint 4.0 has been released! With this release comes a few exciting [changes][0 * [ordered-imports][21] * [prefer-for-of][22] + * **Other rules you might find handy**: * [completed-docs][23] * [cyclomatic-complexity][24] diff --git a/docs/develop/custom-formatters/index.md b/docs/develop/custom-formatters/index.md index a88daad73ca..173c8e3f868 100644 --- a/docs/develop/custom-formatters/index.md +++ b/docs/develop/custom-formatters/index.md @@ -8,7 +8,7 @@ Just like [custom rules][0], additional formatters can also be supplied to TSLin ```ts import * as ts from "typescript"; -import * as Lint from "tslint/lib/lint"; +import * as Lint from "tslint"; export class Formatter extends Lint.Formatters.AbstractFormatter { public format(failures: Lint.RuleFailure[]): string { diff --git a/docs/develop/custom-rules/index.md b/docs/develop/custom-rules/index.md index c9d45c7308f..36806f85425 100644 --- a/docs/develop/custom-rules/index.md +++ b/docs/develop/custom-rules/index.md @@ -8,10 +8,10 @@ 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`. -* The exported class must always be named `Rule` and extend from `Lint.Rules.AbstractRule`. +- Rule identifiers are always kebab-cased. +- Rule files are always camel-cased (`camelCasedRule.ts`). +- Rule files *must* contain the suffix `Rule`. +- The exported class must always be named `Rule` and extend from `Lint.Rules.AbstractRule`. Now, let us first write the rule in TypeScript: @@ -58,12 +58,12 @@ Now that you're written a rule to detect problems, let's modify it to *fix* them Instantiate a `Fix` object and pass it in as an argument to `addFailure`. This snippet replaces the offending import statement with an empty string: ```typescript - // create a fixer for this failure - const replacement = new Lint.Replacement(node.getStart(), node.getWidth(), ""); - const fix = new Lint.Fix("no-imports", [replacement]); +// create a fixer for this failure +const replacement = new Lint.Replacement(node.getStart(), node.getWidth(), ""); +const fix = new Lint.Fix("no-imports", [replacement]); - // create a failure at the current position - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING, fix)); +// create a failure at the current position +this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING, fix)); ``` --- Final notes: diff --git a/docs/rules/max-classes-per-file/index.html b/docs/rules/max-classes-per-file/index.html index 1827aab9dc8..985d4d6824a 100644 --- a/docs/rules/max-classes-per-file/index.html +++ b/docs/rules/max-classes-per-file/index.html @@ -2,8 +2,7 @@ ruleName: max-classes-per-file description: |- - A file may not contain more than the specified number of classes - if the file name does not match the "ignore-filename-pattern" option + A file may not contain more than the specified number of classes rationale: |- Ensures that files have a single responsibility so that that classes each exist in their own files diff --git a/docs/rules/no-unused-variable/index.html b/docs/rules/no-unused-variable/index.html index 3487ff0e49d..abb1bd4e72a 100644 --- a/docs/rules/no-unused-variable/index.html +++ b/docs/rules/no-unused-variable/index.html @@ -1,6 +1,6 @@ --- ruleName: no-unused-variable -deprecationMessage: Use the compiler options --noUnusedParameters and --noUnusedLocals instead. +deprecationMessage: Use the tsc compiler options --noUnusedParameters and --noUnusedLocals instead. description: 'Disallows unused imports, variables, functions and private class members.' optionsDescription: |- diff --git a/package.json b/package.json index 8665ed97a30..c2771cfc3be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "4.0.0-dev.2", + "version": "4.0.0-dev.3", "description": "An extensible static analysis linter for the TypeScript language", "bin": { "tslint": "./bin/tslint" diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 77acc19835a..a1f4dac224b 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -71,8 +71,6 @@ export const rules = { "no-unsafe-finally": true, "no-unused-expression": true, "no-unused-new": true, - // deprecated as of v4.0 - "no-unused-variable": false, // disable this rule as it is very heavy performance-wise and not that useful "no-use-before-declare": false, "no-var-keyword": true, diff --git a/src/configuration.ts b/src/configuration.ts index e295aeada39..1f699b587f4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -19,6 +19,7 @@ import findup = require("findup-sync"); import * as fs from "fs"; import * as path from "path"; import * as resolve from "resolve"; +import { FatalError } from "./error"; import {arrayify, objectify, stripComments} from "./utils"; @@ -32,16 +33,7 @@ export interface IConfigurationFile { rules?: any; } -/** - * Define `Error` here to avoid using `Error` from @types/node. - * Using the `node` version causes a compilation error when this code is used as an npm library if @types/node is not already imported. - */ -export interface Error { - message: string; -} - export interface IConfigurationLoadResult { - error?: Error; path: string; results?: IConfigurationFile; } @@ -119,11 +111,10 @@ export function findConfiguration(configFile: string, inputFilePath: string): IC try { loadResult.results = loadConfigurationFromPath(path); + return loadResult; } catch (error) { - loadResult.error = error; + throw new FatalError(`Failed to load ${path}: ${error.message}`, error); } - - return loadResult; } /** diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index 43f14d14017..ae9f42dec9e 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -73,7 +73,7 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { rulesList = commentTextParts[1].split(/\s+/).slice(1); // remove empty items and potential comment end. - rulesList = rulesList.filter(item => !!item && item.indexOf("*/") === -1); + rulesList = rulesList.filter((item) => !!item && item.indexOf("*/") === -1); // potentially there were no items, so default to `all`. rulesList = rulesList.length > 0 ? rulesList : ["all"]; diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000000..f18aacd485c --- /dev/null +++ b/src/error.ts @@ -0,0 +1,40 @@ +/** + * @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. + */ + +/** + * Generic error typing for EcmaScript errors + * Define `Error` here to avoid using `Error` from @types/node. + * Using the `node` version causes a compilation error when this code is used as an npm library if @types/node is not already imported. + */ +export declare class Error { + public name?: string; + public message: string; + public stack?: string; + constructor(message?: string); +} + +/** + * Used to exit the program and display a friendly message without the callstack. + */ +export class FatalError extends Error { + public static NAME = "FatalError"; + constructor(public message: string, public innerError?: Error) { + super(message); + this.name = FatalError.NAME; + this.stack = new Error().stack; + } +} diff --git a/src/linter.ts b/src/linter.ts index e4fe8d867f1..39512e0659b 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -20,11 +20,11 @@ import * as ts from "typescript"; import { DEFAULT_CONFIG, - IConfigurationFile, findConfiguration, findConfigurationPath, getRelativePath, getRulesDirectories, + IConfigurationFile, loadConfigurationFromPath, } from "./configuration"; import { EnableDisableRulesWalker } from "./enableDisableRules"; @@ -41,7 +41,7 @@ import { arrayify, dedent } from "./utils"; * Linter that can lint multiple files in consecutive runs. */ class Linter { - public static VERSION = "4.0.0-dev.2"; + public static VERSION = "4.0.0-dev.3"; public static findConfiguration = findConfiguration; public static findConfigurationPath = findConfigurationPath; @@ -102,7 +102,6 @@ class Linter { let fileFailures: RuleFailure[] = []; if (this.options.fix) { - this.fixes = []; for (let rule of enabledRules) { let ruleFailures = this.applyRule(rule, sourceFile); const fixes = ruleFailures.map((f) => f.getFix()).filter((f) => !!f); diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index 5a4e79860c7..7c6cebb5047 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -21,6 +21,7 @@ import {camelize} from "underscore.string"; import {getRulesDirectories} from "./configuration"; import {IDisabledInterval, IRule} from "./language/rule/rule"; +import {dedent} from "./utils"; const moduleDirectory = path.dirname(module.filename); const CORE_RULES_DIRECTORY = path.resolve(moduleDirectory, ".", "rules"); @@ -65,26 +66,28 @@ export function loadRules(ruleConfiguration: {[name: string]: any}, } if (notFoundRules.length > 0) { - const ERROR_MESSAGE = ` + const warning = dedent` Could not find implementations for the following rules specified in the configuration: - ${notFoundRules.join("\n")} + ${notFoundRules.join("\n ")} Try upgrading TSLint and/or ensuring that you have all necessary custom rules installed. If TSLint was recently upgraded, you may have old rules configured which need to be cleaned up. `; - throw new Error(ERROR_MESSAGE); - } else if (notAllowedInJsRules.length > 0) { - const JS_ERROR_MESSAGE = ` + console.warn(warning); + } + if (notAllowedInJsRules.length > 0) { + const warning = dedent` Following rules specified in configuration couldn't be applied to .js or .jsx files: - ${notAllowedInJsRules.join("\n")} - + ${notAllowedInJsRules.join("\n ")} Make sure to exclude them from "jsRules" section of your tslint.json. `; - throw new Error(JS_ERROR_MESSAGE); - } else { - return rules; + console.warn(warning); + } + if (rules.length === 0) { + console.warn("No valid rules have been specified"); } + return rules; } export function findRule(name: string, rulesDirectories?: string | string[]) { diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index ff0de18c935..918aab31031 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -58,7 +58,9 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { } public visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void { - this.checkOverloadsAdjacent(node.members, member => member.name && getTextOfPropertyName(member.name)); + this.checkOverloadsAdjacent(node.members, (member) => { + return getTextOfPropertyName(member); + }); super.visitInterfaceDeclaration(node); } @@ -73,7 +75,7 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { } private visitStatements(statements: ts.Statement[]) { - this.checkOverloadsAdjacent(statements, statement => { + this.checkOverloadsAdjacent(statements, (statement) => { if (statement.kind === ts.SyntaxKind.FunctionDeclaration) { const name = (statement as ts.FunctionDeclaration).name; return name && name.text; @@ -83,8 +85,10 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { }); } - private visitMembers(members: (ts.TypeElement | ts.ClassElement)[]) { - this.checkOverloadsAdjacent(members, member => member.name && getTextOfPropertyName(member.name)); + private visitMembers(members: Array) { + this.checkOverloadsAdjacent(members, (member) => { + return getTextOfPropertyName(member); + }); } /** 'getOverloadName' may return undefined for nodes that cannot be overloads, e.g. a `const` declaration. */ @@ -109,19 +113,27 @@ function isLiteralExpression(node: ts.Node): node is ts.LiteralExpression { return node.kind === ts.SyntaxKind.StringLiteral || node.kind === ts.SyntaxKind.NumericLiteral; } -function getTextOfPropertyName(name: ts.PropertyName): string { - switch (name.kind) { +function getTextOfPropertyName(node: ts.InterfaceDeclaration | ts.TypeElement | ts.ClassElement): string { + let nameText: string; + if (node.name == null) { + return null; + } + switch (node.name.kind) { case ts.SyntaxKind.Identifier: - return (name as ts.Identifier).text; + nameText = (node.name as ts.Identifier).text; + break; case ts.SyntaxKind.ComputedPropertyName: - const { expression } = (name as ts.ComputedPropertyName); + const { expression } = (node.name as ts.ComputedPropertyName); if (isLiteralExpression(expression)) { - return expression.text; + nameText = expression.text; } break; default: - if (isLiteralExpression(name)) { - return name.text; + if (isLiteralExpression(node.name)) { + nameText = ( node.name).text; } } + + const suffix = Lint.hasModifier(node.modifiers, ts.SyntaxKind.StaticKeyword) ? " __static__" : ""; + return nameText + suffix; } diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index 5b074c364e9..c51689d56d7 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -118,11 +118,11 @@ function startsWith(commentText: string, changeCase: (str: string) => string) { } function startsWithLowercase(commentText: string) { - return startsWith(commentText, c => c.toLowerCase()); + return startsWith(commentText, (c) => c.toLowerCase()); } function startsWithUppercase(commentText: string) { - return startsWith(commentText, c => c.toUpperCase()); + return startsWith(commentText, (c) => c.toUpperCase()); } function startsWithSpace(commentText: string) { diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index b5d2b265ebd..a43ad746ac2 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -106,7 +106,7 @@ export class CompletedDocsWalker extends Lint.ProgramAwareRuleWalker { const comments = this.getTypeChecker().getSymbolAtLocation(node.name).getDocumentationComment(); - if (comments.map(comment => comment.text).join("").trim() === "") { + if (comments.map((comment) => comment.text).join("").trim() === "") { this.addFailure(this.createDocumentationFailure(node, nodeToCheck)); } } diff --git a/src/rules/cyclomaticComplexityRule.ts b/src/rules/cyclomaticComplexityRule.ts index 4459603f4eb..ddefdee516f 100644 --- a/src/rules/cyclomaticComplexityRule.ts +++ b/src/rules/cyclomaticComplexityRule.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/maxClassesPerFileRule.ts b/src/rules/maxClassesPerFileRule.ts index cd7d112b10b..04b4a933ccc 100644 --- a/src/rules/maxClassesPerFileRule.ts +++ b/src/rules/maxClassesPerFileRule.ts @@ -1,5 +1,5 @@ -import * as Lint from "../index"; -import * as ts from "typescript"; +import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { @@ -7,8 +7,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static metadata: Lint.IRuleMetadata = { ruleName: "max-classes-per-file", description: Lint.Utils.dedent` - A file may not contain more than the specified number of classes - if the file name does not match the "ignore-filename-pattern" option`, + A file may not contain more than the specified number of classes`, rationale: Lint.Utils.dedent` Ensures that files have a single responsibility so that that classes each exist in their own files`, optionsDescription: Lint.Utils.dedent` diff --git a/src/rules/maxFileLineCountRule.ts b/src/rules/maxFileLineCountRule.ts index 0479cc969df..641800d2b11 100644 --- a/src/rules/maxFileLineCountRule.ts +++ b/src/rules/maxFileLineCountRule.ts @@ -14,8 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noUnsafeFinallyRule.ts b/src/rules/noUnsafeFinallyRule.ts index 71339452a89..bfe8669a5a7 100644 --- a/src/rules/noUnsafeFinallyRule.ts +++ b/src/rules/noUnsafeFinallyRule.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -79,7 +79,7 @@ interface IFinallyScope { /** * A collection of `break` or `continue` labels in this scope. */ - labels: Array; + labels: string[]; } /** diff --git a/src/rules/noUnusedExpressionRule.ts b/src/rules/noUnusedExpressionRule.ts index 83c51a222c9..4fa5285e7e3 100644 --- a/src/rules/noUnusedExpressionRule.ts +++ b/src/rules/noUnusedExpressionRule.ts @@ -70,7 +70,7 @@ export class NoUnusedExpressionWalker extends Lint.RuleWalker { if (checkPreviousSiblings) { const siblings: ts.Node[] = []; - ts.forEachChild(node.parent, child => { siblings.push(child); }); + ts.forEachChild(node.parent, (child) => { siblings.push(child); }); return siblings.slice(0, siblings.indexOf(node)).every((n) => NoUnusedExpressionWalker.isDirective(n, false)); } else { return true; diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index a39cbe98516..70c1703d0f3 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -31,7 +31,7 @@ export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { ruleName: "no-unused-variable", - deprecationMessage: "Use the compiler options --noUnusedParameters and --noUnusedLocals instead.", + deprecationMessage: "Use the tsc compiler options --noUnusedParameters and --noUnusedLocals instead.", description: "Disallows unused imports, variables, functions and private class members.", optionsDescription: Lint.Utils.dedent` Three optional arguments may be optionally provided: @@ -197,7 +197,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { if (importClause.namedBindings != null) { if (importClause.namedBindings.kind === ts.SyntaxKind.NamedImports) { let imports = node.importClause.namedBindings as ts.NamedImports; - usedNamedImports = imports.elements.map(e => this.isUsed(e.name.text, e.name.getStart())); + usedNamedImports = imports.elements.map((e) => this.isUsed(e.name.text, e.name.getStart())); } // Avoid deleting the whole statement if there's an import * inside if (importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport) { @@ -206,7 +206,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { } // Delete the entire import statement if named and default imports all unused - if (!usesDefaultImport && usedNamedImports.every(e => !e)) { + if (!usesDefaultImport && usedNamedImports.every((e) => !e)) { this.fail(Rule.FAILURE_TYPE_IMPORT, node.getText(), node.getStart(), this.deleteImportStatement(node)); super.visitImportDeclaration(node); return; @@ -223,7 +223,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { if (importClause.namedBindings != null && importClause.namedBindings.kind === ts.SyntaxKind.NamedImports) { // Delete the entire named imports if all unused, including curly braces. - if (usedNamedImports.every(e => !e)) { + if (usedNamedImports.every((e) => !e)) { const start = importClause.name != null ? importClause.name.getEnd() : importClause.namedBindings.getStart(); this.fail(Rule.FAILURE_TYPE_IMPORT, importClause.namedBindings.getText(), importClause.namedBindings.getStart(), [ this.deleteText(start, importClause.namedBindings.getEnd() - start), diff --git a/src/rules/noVarKeywordRule.ts b/src/rules/noVarKeywordRule.ts index ae1bb2fa8e0..d022c85cf96 100644 --- a/src/rules/noVarKeywordRule.ts +++ b/src/rules/noVarKeywordRule.ts @@ -77,5 +77,5 @@ class NoVarKeywordWalker extends Lint.RuleWalker { private fix = (node: ts.Node) => new Lint.Fix(Rule.metadata.ruleName, [ this.deleteText(node.getStart(), "var".length), this.appendText(node.getStart(), "let"), - ]) + ]); } diff --git a/src/rules/objectLiteralKeyQuotesRule.ts b/src/rules/objectLiteralKeyQuotesRule.ts index 2058d83d136..413581f298b 100644 --- a/src/rules/objectLiteralKeyQuotesRule.ts +++ b/src/rules/objectLiteralKeyQuotesRule.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -77,7 +77,7 @@ export class Rule extends Lint.Rules.AbstractRule { } // This is simplistic. See https://mothereff.in/js-properties for the gorey details. -const IDENTIFIER_NAME_REGEX = /^(?:[\$A-Z_a-z])*$/; +const IDENTIFIER_NAME_REGEX = /^(?:[\$A-Z_a-z])+$/; const NUMBER_REGEX = /^[0-9]+$/; type QuotesMode = "always" | "as-needed" | "consistent" | "consistent-as-needed"; diff --git a/src/rules/objectLiteralShorthandRule.ts b/src/rules/objectLiteralShorthandRule.ts index ed59daa3828..aefc93181a8 100644 --- a/src/rules/objectLiteralShorthandRule.ts +++ b/src/rules/objectLiteralShorthandRule.ts @@ -1,5 +1,5 @@ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/orderedImportsRule.ts b/src/rules/orderedImportsRule.ts index b241f212e21..a78e4fc26e2 100644 --- a/src/rules/orderedImportsRule.ts +++ b/src/rules/orderedImportsRule.ts @@ -89,7 +89,7 @@ export class Rule extends Lint.Rules.AbstractRule { // Convert aBcD --> AbCd function flipCase(x: string): string { - return x.split("").map(char => { + return x.split("").map((char) => { if (char >= "a" && char <= "z") { return char.toUpperCase(); } else if (char >= "A" && char <= "Z") { @@ -143,7 +143,7 @@ function sortByKey(xs: T[], getSortKey: (x: T) => string): T[] { // Transformations to apply to produce the desired ordering of imports. // The imports must be lexicographically sorted after applying the transform. const TRANSFORMS: {[ordering: string]: (x: string) => string} = { - any: () => "", + "any": () => "", "case-insensitive": (x: string) => x.toLowerCase(), "lowercase-first": flipCase, "lowercase-last": (x: string) => x, diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 68183ea3c49..7142a298ded 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import * as Lint from "../index"; import * as ts from "typescript"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -35,89 +35,158 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "Expected a 'for-of' loop instead of a 'for' loop with this simple iteration"; public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); - return this.applyWithWalker(new PreferForOfWalker(sourceFile, this.getOptions(), languageService)); + return this.applyWithWalker(new PreferForOfWalker(sourceFile, this.getOptions())); } } +interface IIncrementorState { + arrayToken: ts.LeftHandSideExpression; + endIncrementPos: number; + onlyArrayAccess: boolean; +} + class PreferForOfWalker extends Lint.RuleWalker { - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { + // a map of incrementors and whether or not they are only used to index into an array reference in the for loop + private incrementorMap: { [name: string]: IIncrementorState }; + + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); + this.incrementorMap = {}; } public visitForStatement(node: ts.ForStatement) { - const arrayAccessNode = this.locateArrayNodeInForLoop(node); - - if (arrayAccessNode !== undefined) { - // Skip arrays thats just loop over a hard coded number - // If we are accessing the length of the array, then we are likely looping over it's values - if (arrayAccessNode.kind === ts.SyntaxKind.PropertyAccessExpression && arrayAccessNode.getLastToken().getText() === "length") { - let incrementorVariable = node.incrementor.getFirstToken(); - if (/\+|-/g.test(incrementorVariable.getText())) { - // If it's formatted as `++i` instead, we need to get the OTHER token - incrementorVariable = node.incrementor.getLastToken(); - } - const arrayToken = arrayAccessNode.getChildAt(0); - const loopSyntaxText = node.statement.getText(); - // Find all usages of the incrementor variable - const fileName = this.getSourceFile().fileName; - const highlights = this.languageService.getDocumentHighlights(fileName, incrementorVariable.getStart(), [fileName]); - - if (highlights && highlights.length > 0) { - // There are *usually* three usages when setting up the for loop, - // so remove those from the count to get the count inside the loop block - const incrementorCount = highlights[0].highlightSpans.length - 3; - - // Find `array[i]`-like usages by building up a regex - const arrayTokenForRegex = arrayToken.getText().replace(".", "\\."); - const incrementorForRegex = incrementorVariable.getText().replace(".", "\\."); - const regex = new RegExp(`${arrayTokenForRegex}\\[\\s*${incrementorForRegex}\\s*\\]`, "g"); - const accessMatches = loopSyntaxText.match(regex); - const matchCount = (accessMatches || []).length; - - // If there are more usages of the array item being access than the incrementor variable - // being used, then this loop could be replaced with a for-of loop instead. - // This means that the incrementor variable is not used on its own anywhere and is ONLY - // used to access the array item. - if (matchCount >= incrementorCount) { - const failure = this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING); - this.addFailure(failure); + const arrayNodeInfo = this.getForLoopHeaderInfo(node); + let indexVariableName: string; + if (arrayNodeInfo != null) { + const { indexVariable, arrayToken } = arrayNodeInfo; + indexVariableName = indexVariable.getText(); + + // store `for` loop state + this.incrementorMap[indexVariableName] = { + arrayToken, + endIncrementPos: node.incrementor.end, + onlyArrayAccess: true, + }; + } + + super.visitForStatement(node); + + if (indexVariableName != null) { + const incrementorState = this.incrementorMap[indexVariableName]; + if (incrementorState.onlyArrayAccess) { + const length = incrementorState.endIncrementPos - node.getStart() + 1; + const failure = this.createFailure(node.getStart(), length, Rule.FAILURE_STRING); + this.addFailure(failure); + } + + // remove current `for` loop state + delete this.incrementorMap[indexVariableName]; + } + } + + public 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; + } + } + super.visitIdentifier(node); + } + + // returns the iterator and array of a `for` loop if the `for` loop is basic. Otherwise, `null` + private getForLoopHeaderInfo(forLoop: ts.ForStatement) { + let indexVariableName: string; + let indexVariable: ts.Identifier; + + // assign `indexVariableName` if initializer is simple and starts at 0 + if (forLoop.initializer != null && forLoop.initializer.kind === ts.SyntaxKind.VariableDeclarationList) { + const syntaxList = forLoop.initializer.getChildAt(1); + if (syntaxList.kind === ts.SyntaxKind.SyntaxList && syntaxList.getChildCount() === 1) { + const assignment = syntaxList.getChildAt(0); + if (assignment.kind === ts.SyntaxKind.VariableDeclaration) { + const value = assignment.getChildAt(2).getText(); + if (value === "0") { + indexVariable = assignment.getChildAt(0); + indexVariableName = indexVariable.getText(); } } } } - super.visitForStatement(node); + // ensure `for` condition + if (indexVariableName == null + || forLoop.condition == null + || forLoop.condition.kind !== ts.SyntaxKind.BinaryExpression + || forLoop.condition.getChildAt(0).getText() !== indexVariableName + || forLoop.condition.getChildAt(1).getText() !== "<") { + + return null; + } + + if (!this.isIncremented(forLoop.incrementor, indexVariableName)) { + return null; + } + + // ensure that the condition checks a `length` property + const conditionRight = forLoop.condition.getChildAt(2); + if (conditionRight.kind === ts.SyntaxKind.PropertyAccessExpression) { + const propertyAccess = conditionRight; + if (propertyAccess.name.getText() === "length") { + return { indexVariable, arrayToken: propertyAccess.expression }; + } + } + + return null; } - private locateArrayNodeInForLoop(forLoop: ts.ForStatement): ts.Node { - // Some oddly formatted (yet still valid!) `for` loops might not have children in the condition - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for - if (forLoop.condition !== undefined) { - let arrayAccessNode = forLoop.condition.getChildAt(2); - // If We haven't found it, maybe it's not a standard for loop, try looking in the initializer for the array - // Something like `for(var t=0, len=arr.length; t < len; t++)` - if (arrayAccessNode.kind !== ts.SyntaxKind.PropertyAccessExpression && forLoop.initializer !== undefined) { - for (let initNode of forLoop.initializer.getChildren()) { - // look in `var t=0, len=arr.length;` - if (initNode.kind === ts.SyntaxKind.SyntaxList) { - for (let initVar of initNode.getChildren()) { - // look in `t=0, len=arr.length;` - if (initVar.kind === ts.SyntaxKind.VariableDeclaration) { - for (let initVarPart of initVar.getChildren()) { - // look in `len=arr.length` - if (initVarPart.kind === ts.SyntaxKind.PropertyAccessExpression) { - arrayAccessNode = initVarPart; - } - } - } - } + private isIncremented(node: ts.Node, indexVariableName: string) { + if (node == null) { + return false; + } + + // ensure variable is incremented + if (node.kind === ts.SyntaxKind.PrefixUnaryExpression) { + const incrementor = node; + if (incrementor.operator === ts.SyntaxKind.PlusPlusToken && incrementor.operand.getText() === indexVariableName) { + // x++ + return true; + } + } else if (node.kind === ts.SyntaxKind.PostfixUnaryExpression) { + const incrementor = node; + if (incrementor.operator === ts.SyntaxKind.PlusPlusToken && incrementor.operand.getText() === indexVariableName) { + // ++x + return true; + } + } else if (node.kind === ts.SyntaxKind.BinaryExpression) { + const binaryExpression = node; + if (binaryExpression.operatorToken.getText() === "+=" + && binaryExpression.left.getText() === indexVariableName + && binaryExpression.right.getText() === "1") { + // x += 1 + return true; + } + if (binaryExpression.operatorToken.getText() === "=" + && binaryExpression.left.getText() === indexVariableName) { + const addExpression = binaryExpression.right; + if (addExpression.operatorToken.getText() === "+") { + if (addExpression.right.getText() === indexVariableName && addExpression.left.getText() === "1") { + // x = 1 + x + return true; + } else if (addExpression.left.getText() === indexVariableName && addExpression.right.getText() === "1") { + // x = x + 1 + return true; } } } - return arrayAccessNode; } else { - return undefined; + return false; } + return false; } } diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 35d50dd61bd..845cf4be0f7 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -66,7 +66,7 @@ export class Rule extends Lint.Rules.AbstractRule { } class TrailingCommaWalker extends Lint.RuleWalker { - private static SYNTAX_LIST_WRAPPER_TOKENS: [ts.SyntaxKind, ts.SyntaxKind][] = [ + private static SYNTAX_LIST_WRAPPER_TOKENS: Array<[ts.SyntaxKind, ts.SyntaxKind]> = [ [ts.SyntaxKind.OpenBraceToken, ts.SyntaxKind.CloseBraceToken], [ts.SyntaxKind.OpenBracketToken, ts.SyntaxKind.CloseBracketToken], [ts.SyntaxKind.OpenParenToken, ts.SyntaxKind.CloseParenToken], @@ -189,8 +189,8 @@ class TrailingCommaWalker extends Lint.RuleWalker { // as opposed to optionals alongside it. So instead of children[i + 1] having // [ PropertySignature, Semicolon, PropertySignature, Semicolon ], the AST is // [ PropertySignature, PropertySignature], where the Semicolons are under PropertySignature - const hasSemicolon = grandChildren.some(grandChild => { - return grandChild.getChildren().some(ggc => ggc.kind === ts.SyntaxKind.SemicolonToken); + const hasSemicolon = grandChildren.some((grandChild) => { + return grandChild.getChildren().some((ggc) => ggc.kind === ts.SyntaxKind.SemicolonToken); }); if (!hasSemicolon) { diff --git a/src/rules/typedefWhitespaceRule.ts b/src/rules/typedefWhitespaceRule.ts index 3713d9db65d..c7cbb094262 100644 --- a/src/rules/typedefWhitespaceRule.ts +++ b/src/rules/typedefWhitespaceRule.ts @@ -30,7 +30,7 @@ const SPACE_OBJECT = { properties: { "call-signature": SPACE_OPTIONS, "index-signature": SPACE_OPTIONS, - parameter: SPACE_OPTIONS, + "parameter": SPACE_OPTIONS, "property-declaration": SPACE_OPTIONS, "variable-declaration": SPACE_OPTIONS, }, diff --git a/src/test.ts b/src/test.ts index de9f71d133d..90c8e94cc75 100644 --- a/src/test.ts +++ b/src/test.ts @@ -119,7 +119,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ const stat = fs.statSync(fixedFile); if (stat.isFile()) { fixedFileText = fs.readFileSync(fixedFile, "utf8"); - const fixes = failures.filter(f => f.hasFix()).map(f => f.getFix()); + const fixes = failures.filter((f) => f.hasFix()).map((f) => f.getFix()); newFileText = Fix.applyAll(fileTextWithoutMarkup, fixes); } } catch (e) { diff --git a/src/test/lintError.ts b/src/test/lintError.ts index 46968be2599..5f90a4b3a81 100644 --- a/src/test/lintError.ts +++ b/src/test/lintError.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Error } from "../configuration"; +import { Error } from "../error"; export interface PositionInFile { line: number; diff --git a/src/test/parse.ts b/src/test/parse.ts index cff0bde17d8..d0f8c8d2abc 100644 --- a/src/test/parse.ts +++ b/src/test/parse.ts @@ -24,7 +24,7 @@ import { parseLine, printLine, } from "./lines"; -import {LintError, errorComparator, lintSyntaxError} from "./lintError"; +import {errorComparator, LintError, lintSyntaxError} from "./lintError"; /** * Takes the full text of a .lint file and returns the contents of the file diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index ab8d3d259c1..c8f2dc3e9a6 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -26,6 +26,7 @@ import { DEFAULT_CONFIG, findConfiguration, } from "./configuration"; +import { FatalError } from "./error"; import * as Linter from "./linter"; import { consoleTestResultHandler, runTest } from "./test"; import { updateNotifierCheck } from "./updateNotifier"; @@ -43,58 +44,58 @@ let processed = optimist } }) .options({ - c: { + "c": { alias: "config", describe: "configuration file", }, - e: { + "e": { alias: "exclude", describe: "exclude globs from path expansion", type: "string", }, - fix: { - describe: "Fixes linting errors for select rules. This may overwrite linted files", + "fix": { + describe: "fixes linting errors for select rules (this may overwrite linted files)", type: "boolean", }, - force: { + "force": { describe: "return status code 0 even if there are lint errors", type: "boolean", }, - h: { + "h": { alias: "help", describe: "display detailed help", }, - i: { + "i": { alias: "init", describe: "generate a tslint.json config file in the current working directory", }, - o: { + "o": { alias: "out", describe: "output file", }, - project: { + "project": { describe: "tsconfig.json file", }, - r: { + "r": { alias: "rules-dir", describe: "rules directory", }, - s: { + "s": { alias: "formatters-dir", describe: "formatters directory", }, - t: { + "t": { alias: "format", default: "prose", describe: "output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist)", }, - test: { + "test": { describe: "test that tslint produces the correct output for the specified directory", }, "type-check": { describe: "enable type checking when linting a project", }, - v: { + "v": { alias: "version", describe: "current version", }, @@ -259,13 +260,7 @@ const processFiles = (files: string[], program?: ts.Program) => { const contents = fs.readFileSync(file, "utf8"); const configLoad = findConfiguration(possibleConfigAbsolutePath, file); - - if (configLoad.results) { - linter.lint(file, contents, configLoad.results); - } else { - console.error(`Failed to load ${configLoad.path}: ${configLoad.error.message}`); - process.exit(1); - } + linter.lint(file, contents, configLoad.results); } const lintResult = linter.getResult(); @@ -332,4 +327,13 @@ files = files .map((file: string) => glob.sync(file, { ignore: ignorePatterns, nodir: true })) .reduce((a: string[], b: string[]) => a.concat(b)); -processFiles(files, program); +try { + processFiles(files, program); +} catch (error) { + if (error.name === FatalError.NAME) { + console.error(error.message); + process.exit(1); + } + // rethrow unhandled error + throw error; +} diff --git a/src/utils.ts b/src/utils.ts index c8b58e36c79..7279b2a7396 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -55,7 +55,7 @@ export function dedent(strings: TemplateStringsArray, ...values: string[]) { } // find the smallest indent, we don't want to remove all leading whitespace - const indent = Math.min(...match.map(el => el.length)); + const indent = Math.min(...match.map((el) => el.length)); const regexp = new RegExp("^[ \\t]{" + indent + "}", "gm"); fullString = indent > 0 ? fullString.replace(regexp, "") : fullString; return fullString; diff --git a/test/configurationTests.ts b/test/configurationTests.ts index 1a4d28eda2a..11134abcacb 100644 --- a/test/configurationTests.ts +++ b/test/configurationTests.ts @@ -16,7 +16,7 @@ import * as fs from "fs"; -import { IConfigurationFile, extendConfigurationFile, loadConfigurationFromPath } from "../src/configuration"; +import { extendConfigurationFile, IConfigurationFile, loadConfigurationFromPath } from "../src/configuration"; import { createTempFile } from "./utils"; describe("Configuration", () => { diff --git a/test/executable/executableTests.ts b/test/executable/executableTests.ts index 08655c96b56..2b9288c827b 100644 --- a/test/executable/executableTests.ts +++ b/test/executable/executableTests.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { createTempFile, denormalizeWinPath } from "../utils"; import * as cp from "child_process"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; +import { createTempFile, denormalizeWinPath } from "../utils"; // when tests are run with mocha from npm scripts CWD points to project root const EXECUTABLE_DIR = path.resolve(process.cwd(), "test", "executable"); @@ -28,6 +28,8 @@ const TEMP_JSON_PATH = path.resolve(EXECUTABLE_DIR, "tslint.json"); /* tslint:disable:only-arrow-functions */ describe("Executable", function() { this.slow(3000); // the executable is JIT-ed each time it runs; avoid showing slowness warnings + this.timeout(4000); + describe("Files", () => { it("exits with code 1 if no arguments passed", (done) => { execCli([], (err, stdout, stderr) => { diff --git a/test/external/tslint-test-custom-rules/rules/ruleOneRule.js b/test/external/tslint-test-custom-rules/rules/ruleOneRule.js index 1d2ff62d1af..9b8b16e0fb8 100644 --- a/test/external/tslint-test-custom-rules/rules/ruleOneRule.js +++ b/test/external/tslint-test-custom-rules/rules/ruleOneRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint/lib/lint"); +var Lint = require("tslint"); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/external/tslint-test-custom-rules/rules/ruleThreeRule.js b/test/external/tslint-test-custom-rules/rules/ruleThreeRule.js index 1d2ff62d1af..9b8b16e0fb8 100644 --- a/test/external/tslint-test-custom-rules/rules/ruleThreeRule.js +++ b/test/external/tslint-test-custom-rules/rules/ruleThreeRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint/lib/lint"); +var Lint = require("tslint"); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/external/tslint-test-custom-rules/rules/ruleTwoRule.js b/test/external/tslint-test-custom-rules/rules/ruleTwoRule.js index 1d2ff62d1af..9b8b16e0fb8 100644 --- a/test/external/tslint-test-custom-rules/rules/ruleTwoRule.js +++ b/test/external/tslint-test-custom-rules/rules/ruleTwoRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint/lib/lint"); +var Lint = require("tslint"); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/files/custom-rules-2/noFailRule.js b/test/files/custom-rules-2/noFailRule.js index 1d2ff62d1af..9b8b16e0fb8 100644 --- a/test/files/custom-rules-2/noFailRule.js +++ b/test/files/custom-rules-2/noFailRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint/lib/lint"); +var Lint = require("tslint"); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/files/custom-rules/alwaysFailRule.js b/test/files/custom-rules/alwaysFailRule.js index ee3abbe634d..b6d3d25921c 100644 --- a/test/files/custom-rules/alwaysFailRule.js +++ b/test/files/custom-rules/alwaysFailRule.js @@ -3,7 +3,7 @@ var __extends = (this && this.__extends) || function (d, b) { function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; -var Lint = require("tslint/lib/lint"); +var Lint = require("tslint"); var Rule = (function (_super) { __extends(Rule, _super); function Rule() { diff --git a/test/ruleLoaderTests.ts b/test/ruleLoaderTests.ts index fc75dc77e27..21f784254f4 100644 --- a/test/ruleLoaderTests.ts +++ b/test/ruleLoaderTests.ts @@ -23,51 +23,34 @@ describe("Rule Loader", () => { it("loads core rules", () => { const validConfiguration: {[name: string]: any} = { "class-name": true, - eofline: true, - forin: false, + "eofline": true, + "forin": false, "no-debugger": true, - quotemark: "single", + "quotemark": "single", }; const rules = loadRules(validConfiguration, {}, RULES_DIRECTORY); assert.equal(rules.length, 5); }); - it("throws if an invalid rule is found", () => { + it("ignores invalid rules", () => { const invalidConfiguration: {[name: string]: any} = { - invalidConfig1: true, - invalidConfig2: false, - }; - - assert.throws( - () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY), - /invalidConfig1\ninvalidConfig2/, - ); - }); - - it("doesn't ignore leading or trailing underscores or dashes", () => { - /* tslint:disable:object-literal-sort-keys */ - const invalidConfiguration: {[name: string]: any} = { - _indent: 6, - forin_: true, - "-quotemark": "single", - "eofline-": true, + "class-name": true, + "invalidConfig1": true, + "invalidConfig2": false, }; - /* tslint:enable:object-literal-sort-keys */ - assert.throws( - () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY), - /_indent\nforin_\n-quotemark\neofline-/, - ); + const rules = loadRules(invalidConfiguration, {}, [RULES_DIRECTORY]); + assert.equal(rules.length, 1); }); it("works with rulesDirectory argument as an Array", () => { const validConfiguration: {[name: string]: any} = { "class-name": true, - eofline: true, - forin: false, + "eofline": true, + "forin": false, "no-debugger": true, - quotemark: "single", + "quotemark": "single", }; const rules = loadRules(validConfiguration, {}, [RULES_DIRECTORY]); @@ -82,15 +65,4 @@ describe("Rule Loader", () => { const rules = loadRules(validConfiguration, {}, RULES_DIRECTORY, true); assert.equal(rules.length, 1); }); - - it("throws if an invalid rule is adopted", () => { - const invalidConfiguration: {[name: string]: any} = { - "array-type": [true, "array"], - }; - - assert.throws( - () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY, true), - /array-type/, - ); - }); }); diff --git a/test/rules/adjacent-overload-signatures/test.ts.lint b/test/rules/adjacent-overload-signatures/test.ts.lint index 9b65ef3d1b2..de6a26b2f28 100644 --- a/test/rules/adjacent-overload-signatures/test.ts.lint +++ b/test/rules/adjacent-overload-signatures/test.ts.lint @@ -111,3 +111,9 @@ declare namespace N { export function a(x: number): void; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] } + +class Foo { + public static bar() {} + constructor() {} + public bar() {} +} \ No newline at end of file diff --git a/test/rules/object-literal-key-quotes/as-needed/test.js.lint b/test/rules/object-literal-key-quotes/as-needed/test.js.lint index e6dc627b5d0..86c24145018 100644 --- a/test/rules/object-literal-key-quotes/as-needed/test.js.lint +++ b/test/rules/object-literal-key-quotes/as-needed/test.js.lint @@ -19,4 +19,5 @@ const o = { "0x0": 0, "true": 0, // failure ~~~~~~ [Unnecessarily quoted property 'true' found.] + '': 'always quote the empty string', }; diff --git a/test/rules/object-literal-key-quotes/as-needed/test.ts.lint b/test/rules/object-literal-key-quotes/as-needed/test.ts.lint index e6dc627b5d0..86c24145018 100644 --- a/test/rules/object-literal-key-quotes/as-needed/test.ts.lint +++ b/test/rules/object-literal-key-quotes/as-needed/test.ts.lint @@ -19,4 +19,5 @@ const o = { "0x0": 0, "true": 0, // failure ~~~~~~ [Unnecessarily quoted property 'true' found.] + '': 'always quote the empty string', }; diff --git a/test/rules/prefer-for-of/test.ts.lint b/test/rules/prefer-for-of/test.ts.lint index 0457f270347..ac67a99dacb 100644 --- a/test/rules/prefer-for-of/test.ts.lint +++ b/test/rules/prefer-for-of/test.ts.lint @@ -1,70 +1,46 @@ function sampleFunc() { - //This loop only uses the iterator to access the array item, so we can recommend a for-of loop here - for (var a = 0; a <= obj.arr.length; a++) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // This loop only uses the iterator to access the array item, so we can recommend a for-of loop here + for (var a = 0; a < obj.arr.length; a++) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] console.log(obj.arr[a]); -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } -~~~~~ [0] - //Same as above, but no curly braces - for (var b = 0; b <= obj.arr.length; b++) console.log(obj.arr[b]); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + // Same as above, but no curly braces + for (var b = 0; b < obj.arr.length; b++) console.log(obj.arr[b]); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] - //the index is used by itself, so a normal for loop is allowed here - for (var c = 0; c <= arr.length; c++) { + // the index is used by itself, so a normal for loop is allowed here + for (var c = 0; c < arr.length; c++) { doMath(c); } - //Same as above, but no curly braces - for (var d = 0; d <= arr.length; d++) doMath(d); + // Same as above, but no curly braces + for (var d = 0; d < arr.length; d++) doMath(d); - //the index is used by itself, so a normal for loop is allowed here - for (var e = 0; e <= arr.length; e++) { + // the index is used by itself, so a normal for loop is allowed here + for (var e = 0; e < arr.length; e++) { if(e > 5) { doMath(e); } console.log(arr[e]); } - //This iterates off of a hard-coded number and should be allowed + // This iterates off of a hard-coded number and should be allowed for (var f = 0; f <= 40; f++) { doMath(f); } - //Same as above, but no curly braces + // Same as above, but no curly braces for (var g = 0; g <= 40; g++) doMath(g); - //Loop set up different, but uses the index alone - this is ok - for(var h=0, len=arr.length; h < len; h++) { - doMath(h); - } - - //Same as above, but no curly braces - for(var i=0, len=arr.length; i < len; i++) doMath(i); - - //Loop set up different, but uses the index alone - this is ok - for(var j=0, len=arr.length; j < len; j++){ - if(j > 5) { - doMath(j); - } - console.log(arr[j]); - } + // multiple operations in the initializer + for(var h=0, len=arr.length; h < len; h++) {} - //Loop set up different, only uses the index to access the array - this should fail - for(var k=0, len=arr.length; k < len; k++) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - console.log(arr[k]); -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - } -~~~~~ [0] - - //Same as above, but no curly braces - for(var l=0, len=arr.length; l < len; l++) console.log(arr[l]); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + // Same as above, but no curly braces + for(var i=0, len=arr.length; i < len; i++) arr[i]; - //Odd for loop setups + // Odd for loop setups var m = 0; for (;;) { if (m > 3) break; @@ -79,28 +55,68 @@ function sampleFunc() { var o = 0; for (; o < arr.length; o++) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ console.log(arr[o]); -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } -~~~~~ [0] - //Prefix incrementor + // Prefix incrementor for(let p = 0; p < arr.length; ++p) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] arr[p].whatever(); -~~~~~~~~~~~~~~~~~~~~~~~~~~ } -~~~~~ [0] - //For in loops ARE allowed + // empty + for(let x = 0; x < arr.length; x++) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + + // missing + for(; x < arr.length; x++) {} + for(let x = 0;; x++) {} + for(let x = 0; x < arr.length;) {} + + // mismatch + for(let x = 0; NOTX < arr.length; x++) {} + for(let x = 0; x < arr.length; NOTX++) {} + for(let NOTX = 0; x < arr.length; x++) {} + + // decrement + for(let x = 0; x < arr.length; x--) {} + + // not `<` + for(let x = 0; x <= arr.length; x++) {} + + // wrong starting point + for(let x = 1; x < arr.length; x++) {} + + // not `length` property + for(let x = 0; x < arr.length(); x++) {} + + // alternate incrementor + for(let x = 0; x < arr.length; x+=1) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + for(let x = 0; x < arr.length; x=x+1) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + for(let x = 0; x < arr.length; x=1+x) {} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + + // adds too much + for(let x = 0; x < arr.length; x+=11) {} + for(let x = 0; x < arr.length; x=x+11) {} + + for(let x = 0; x < arr.length; x++) { + x++; + } + + // unexpected condition + for(let x = 0; true; x++) {} + + // For-in loops ARE allowed for (var q in obj) { if (obj.hasOwnProperty(q)) { console.log(q); } } - //For of loops ARE allowed + // For-of loops ARE allowed for (var r of arr) { console.log(r); } diff --git a/tslint.json b/tslint.json index f0eae7a1393..c496ae41946 100644 --- a/tslint.json +++ b/tslint.json @@ -2,6 +2,7 @@ "extends": "tslint:latest", "rules": { "interface-name": false, + "max-classes-per-file": false, "max-line-length": [true, 140], "member-ordering": [true, "public-before-private",