diff --git a/.gitignore b/.gitignore index 60b5c5e8d98..7b4feb5fd1c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /build/ /docs/site/ /scripts/*.js +/scripts/*.js.map /lib/ /test/executable/tslint.json node_modules/ diff --git a/.npmignore b/.npmignore index fe6cf8753f5..d76bbd86186 100644 --- a/.npmignore +++ b/.npmignore @@ -11,10 +11,10 @@ .vscode appveyor.yml circle.yml -Gruntfile.js tslint.json /build/ /docs/ +/scripts/ /src/ /test/ tscommand*.txt diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..a3906afe938 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,89 @@ +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Debug CLI", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/build/src/tslint-cli.js", + "stopOnEntry": false, + "args": [], + "cwd": "${workspaceRoot}", + "preLaunchTask": "tsc", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "console": "internalConsole", + "sourceMaps": true, + "outFiles": [], + "outDir": "${workspaceRoot}/build" + }, + { + "name": "Debug Mocha Tests", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "args": ["--reporter", "spec", "--colors", "--no-timeouts", "build/test/**/*Tests.js", "build/test/assert.js"], + "cwd": "${workspaceRoot}", + "preLaunchTask": "tsc", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "console": "internalConsole", + "sourceMaps": true, + "outFiles": [], + "outDir": "${workspaceRoot}/build" + }, + { + "name": "Debug Rule Tests", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/test/ruleTestRunner.ts", + "stopOnEntry": false, + "args": ["run", "test"], + "cwd": "${workspaceRoot}", + "preLaunchTask": "tsc", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "console": "internalConsole", + "sourceMaps": true, + "outFiles": [], + "outDir": "${workspaceRoot}/build" + }, + { + "name": "Debug Document Generation", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/scripts/buildDocs.ts", + "stopOnEntry": false, + "args": ["run", "test"], + "cwd": "${workspaceRoot}", + "preLaunchTask": "tsc", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "console": "internalConsole", + "sourceMaps": true, + "outFiles": [], + "outDir": "${workspaceRoot}/build" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5a7f3b3425a..f3aafc3f3d3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,19 +1,11 @@ -// Available variables which can be used inside of strings. -// ${workspaceRoot}: the root folder of the team -// ${file}: the current opened file -// ${fileBasename}: the current opened file's basename -// ${fileDirname}: the current opened file's dirname -// ${fileExtname}: the current opened file's extension -// ${cwd}: the current working directory of the spawned process - -// A task runner that calls scripts/tsc-wrapper.js. The wrapper script traverses -// up the file system until a valid `tsconfig.json` is found, and runs `tsc` from -// node_modules with that path as the root -{ - "version": "0.1.0", - "command": "${workspaceRoot}/scripts/tsc-wrapper.js", - "isShellCommand": true, - "showOutput": "silent", - "args": ["${workspaceRoot}", "${fileDirname}"], - "problemMatcher": "$tsc" -} +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "tsc", + "isShellCommand": true, + "args": ["-w", "-p", "test"], + "showOutput": "silent", + "isWatching": true, + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9713ca2d2..21ff0dce1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,122 @@ Change Log === +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) + * [enhancement] Changed order of applied configuration files for the `extends` array to make it more intuitive. (#1503) + * [enhancement] Changed TypeScript peer dependency to >= 2.0.0 (#1710) +* [new-rule] `completed-docs` rule added (#1644) +* [new-fixer] `ordered-imports` auto fixed (#1640) +* [new-fixer] `arrow-parens` auto fixed (#1731) +* [rule-change] `indent` rule now ignores template strings (#1611) +* [new-rule-option] `object-literal-key-quotes` adds the options `consistent` and `consistent-as-needed` (#1733) +* [enhancement] `--fix` option added to automatically fix selected rules (#1697) +* [enhancement] Updated recommend rules (#1717) +* [enhancement] `adjacent-overload-signatures` now works with classes, source files, modules, and namespaces (#1707) +* [enhancement] Users are notified if they are using an old TSLint version (#1696) +* [bugfix] Lint `.jsx` files if `jsRules` are configured (#1714) +* [bugfix] Command line glob patterns now handle single quotes (#1679) + +Thanks to our contributors! +* Andrii Dieiev +* Andy +* Chris Barr +* Davie Schoots +* Jordan Hawker +* Josh Goldberg +* Stepan Riha +* Yuichi Nukiyama + v4.0.0-dev.1 --- -* TODO +* **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) + * [removed-rule] Removed `use-strict` rule (#678) + * [removed-rule] Removed `label-undefined` rule; covered by compiler (#1614) + * [enhancement] Renamed `no-constructor-vars` to `no-parameter-properties` (#1296) + * [rule-change] The `orderedImports` rule now sorts relative modules below non-relative modules (#1640) +* **Deprecated** + * [deprecated] `no-unused-variable` rule. This is checked by the TypeScript v2 compiler using the flags [`--noUnusedParameters` and `--noUnusedLocals`](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#flag-unused-declarations-with---nounusedparameters-and---nounusedlocals). (#1481) +* [enhancement] Lint .js files (#1515) +* [new-fixer] `no-var-keyword` replaces `var` with `let` (#1547) +* [new-fixer] `trailing-comma` auto fixed (#1546) +* [new-fixer] `no-unused-variable` auto fixed for imports (#1568) +* [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) +* [bugfix] `no-empty` rule fixed when parameter has readonly modifier +* [bugfix] `no-namespace` rule: do not flag nested or .d.ts namespaces (#1571) + +Thanks to our contributors! + +* Alex Eagle +* Andrii Dieiev +* Ben Coveney +* Boris Aranovich +* Chris Barr +* Cyril Gandon +* Evgeniy Zhukovskiy +* Jay Anslow +* Kunal Marwaha +* Martin Probst +* Mingye Wang +* Raghav Katyal +* Sean Dawson +* Yuichi Nukiyama +* jakpaw v4.0.0-dev.0 --- -* TODO +* **BREAKING CHANGES** + * [enhancement] Drop support for configuration via package.json (#1579) + * [removed-rule] Removed `no-duplicate-key` rule; covered by compiler (#1109) + * [enhancement] Call formatter once for all file results. Format output may be different (#656) + * [rule-change] `trailing-comma` supports function declarations, expressions, and types (#1486) + * [rule-change] `object-literal-sort-keys` now sorts quoted keys (#1529) + * [rule-change] `semicolon` now processes type aliases (#1475) + * [rule-change] `no-var-keyword` now rejects `export var` statements (#1256) + * [rule-change] `semicolon` now requires semicolon for function declaration with no body (#1447) +* [new-formatter] `fileslist` formatter writes a list of files with errors without position or error type specifics (#1558) +* [new-rule] `cyclomaticComplexity`, enforces a threshold of cyclomatic complexity.] (#1464) +* [new-rule] `prefer-for-of`, which errors when `for(var x of y)` can be used instead of `for(var i = 0; i < y.length; i++)` (#1335) +* [new-rule] `array-type`, which can require using either `T[]' or 'Array' for arrays (#1498) +* [rule-change] `object-literal-sort-keys` checks multiline objects only (#1642) +* [rule-change] `ban` rule now can ban global functions (#327) +* [bugfix] always write lint result, even if using formatter (#1353) +* [bugfix] npm run test:bin fails on Windows (#1635) +* [bugfix] Don't enforce trailing spaces on newlines in typedef-whitespace rule (#1531) +* [bugfix] `jsdoc` rule should not match arbitrary comments (#1543) +* [bugfix] `one-line` rule errors when declaring wildcard ambient modules (#1425) + +Thanks to our contributors! + +* Alex Eagle +* Andrii Dieiev +* Andy Hanson +* Ben Coveney +* Boris Aranovich +* Chris Barr +* Christian Dreher +* Claas Augner +* Josh Goldberg +* Martin Probst +* Mike Deverell +* Nina Hartmann +* Satoshi Amemiya +* Scott Wu +* Steve Van Opstal +* Umar Bolatov +* Vladimir Matveev +* Yui v3.15.1 --- diff --git a/LICENSE b/LICENSE index 402690572fd..8dada3edaf5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -179,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" + boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -187,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2014 Palantir Technologies + Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index eb297d9b95d..01c8c313af2 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,15 @@ The configuration file specifies which rules are enabled and their options. Thes "extends": "tslint:latest", "rules": { /* - * Any rules specified here will override those from the base config we are extending + * Any rules specified here will override those from the base config we are extending. */ - "no-parameter-properties": true + "curly": true + }, + "jsRules": { + /* + * Any rules specified here will override those from the base config we are extending. + */ + "curly": true }, "rulesDirectory": [ /* @@ -108,16 +114,17 @@ Options: ``` -c, --config configuration file +-e, --exclude exclude globs from path expansion +--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 -o, --out output file +--project tsconfig.json file -r, --rules-dir rules directory -s, --formatters-dir formatters directory --e, --exclude exclude globs from path expansion --t, --format output format (prose, json, verbose, pmd, msbuild, checkstyle) [default: "prose"] +-t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist) [default: "prose"] --test test that tslint produces the correct output for the specified directory ---project path to tsconfig.json file --type-check enable type checking when linting a project -v, --version current version ``` @@ -145,6 +152,9 @@ tslint accepts the following command-line options: This option can be supplied multiple times if you need multiple globs to indicate which files to exclude. +--fix: + Fixes linting errors for select rules. This may overwrite linted files. + --force: Return status code 0 even if there are any lint errors. Useful while running as npm script. @@ -301,15 +311,19 @@ If we don't have all the rules you're looking for, you can either write your own TSLint ships with a set of core rules that can be configured. However, users are also allowed to write their own rules, which allows them to enforce specific behavior not covered by the core of TSLint. TSLint's internal rules are itself written to be pluggable, so adding a new rule is as simple as creating a new rule file named by convention. New rules can be written in either TypeScript or JavaScript; if written in TypeScript, the code must be compiled to JavaScript before invoking TSLint. -Rule names are always camel-cased and *must* contain the suffix `Rule`. 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 can be referenced in `tslint.json` in their kebab-case forms, so `"no-imports": true` would turn on the rule. +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`. -Now, let us first write the rule in TypeScript. A few things to note: -- We import `tslint/lib/lint` to get the whole `Lint` namespace instead of just the `Linter` class. -- The exported class must always be named `Rule` and extend from `Lint.Rules.AbstractRule`. +Now, let us first write the rule in TypeScript: ```typescript import * as ts from "typescript"; -import * as Lint from "tslint/lib/lint"; +import * as Lint from "tslint"; export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "import statement forbidden"; @@ -336,11 +350,13 @@ Given a walker, TypeScript's parser visits the AST using the visitor pattern. So We still need to hook up this new rule to TSLint. First make sure to compile `noImportsRule.ts`: ```bash -tsc -m commonjs --noImplicitAny noImportsRule.ts node_modules/tslint/lib/tslint.d.ts +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. + Final notes: - Core rules cannot be overwritten with a custom implementation. @@ -373,9 +389,10 @@ Development #### Quick Start ```bash -git clone git@github.com:palantir/tslint.git +git clone git@github.com:palantir/tslint.git --config core.autocrlf=input --config core.eol=lf npm install -grunt +npm run compile +npm run test ``` #### `next` branch @@ -391,7 +408,9 @@ Creating a new release 1. Bump the version number in `package.json` and `src/tslintMulti.ts` 2. Add release notes in `CHANGELOG.md` -3. Run `grunt` to build the latest sources +3. `npm run verify` to build the latest sources 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/formatters.json b/docs/_data/formatters.json index 83a81e7bf9d..c3541f5d75c 100644 --- a/docs/_data/formatters.json +++ b/docs/_data/formatters.json @@ -1,11 +1,4 @@ [ - { - "formatterName": "applyFixes", - "description": "Automatically fixes lint failures.", - "descriptionDetails": "\nModifies source files and applies fixes for lint failures where possible. Changes\nshould be tested as not all fixes preserve semantics.", - "sample": "\nAll done. Remember to test the changes, as not all fixes preserve semantics.", - "consumer": "machine" - }, { "formatterName": "checkstyle", "description": "Formats errors as through they were Checkstyle output.", diff --git a/docs/_data/rules.json b/docs/_data/rules.json index a431bba3525..249191ad953 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -7,7 +7,9 @@ "optionExamples": [ "true" ], - "type": "typescript" + "rationale": "Improves readability and organization by grouping naturally related items together.", + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "align", @@ -30,7 +32,28 @@ "optionExamples": [ "[true, \"parameters\", \"statements\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false + }, + { + "ruleName": "array-type", + "description": "Requires using either 'T[]' or 'Array' for arrays.", + "optionsDescription": "\nOne of the following arguments must be provided:\n\n* `\"array\"` enforces use of `T[]` for all types T.\n* `\"generic\"` enforces use of `Array` for all types T.\n* `\"array-simple\"` enforces use of `T[]` if `T` is a simple type (primitive or type reference).", + "options": { + "type": "string", + "enum": [ + "array", + "generic", + "array-simple" + ] + }, + "optionExamples": [ + "[true, array]", + "[true, generic]", + "[true, array-simple]" + ], + "type": "style", + "typescriptOnly": true }, { "ruleName": "arrow-parens", @@ -41,34 +64,29 @@ "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "ban", - "description": "Bans the use of specific functions.", - "descriptionDetails": "At this time, there is no way to disable global methods with this rule.", - "optionsDescription": "A list of `['object', 'method', 'optional explanation here']` which ban `object.method()`.", + "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()`.", "options": { "type": "list", "listType": { "type": "array", - "arrayMembers": [ - { - "type": "string" - }, - { - "type": "string" - }, - { - "type": "string" - } - ] + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 3 } }, "optionExamples": [ - "[true, [\"someObject\", \"someFunction\"], [\"someObject\", \"otherFunction\", \"Optional explanation\"]]" + "[true, [\"someGlobalMethod\"], [\"someObject\", \"someFunction\"], \n [\"someObject\", \"otherFunction\", \"Optional explanation\"]]" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "class-name", @@ -79,7 +97,8 @@ "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "comment-format", @@ -102,7 +121,31 @@ "optionExamples": [ "[true, \"check-space\", \"check-lowercase\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false + }, + { + "ruleName": "completed-docs", + "description": "Enforces documentation for important items be filled out.", + "optionsDescription": "\nEither `true` to enable for all, or any of\n`[\"classes\", \"functions\", \"methods\", \"properties\"]\nto choose individual ones.`", + "options": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "classes", + "functions", + "methods", + "properties" + ] + } + }, + "optionExamples": [ + "true", + "[true, [\"classes\", \"functions\"]" + ], + "type": "style", + "typescriptOnly": false }, { "ruleName": "curly", @@ -113,7 +156,25 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false + }, + { + "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", + "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": { + "type": "number", + "minimum": "$(Rule.MINIMUM_THRESHOLD)" + }, + "optionExamples": [ + "true", + "[true, 20]" + ], + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "eofline", @@ -124,7 +185,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "file-header", @@ -134,9 +196,10 @@ "type": "string" }, "optionExamples": [ - "\"true\", \"Copyright \\d{4}\"" + "[true, \"Copyright \\\\d{4}\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "forin", @@ -147,7 +210,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "indent", @@ -164,7 +228,8 @@ "optionExamples": [ "[true, \"spaces\"]" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "interface-name", @@ -182,7 +247,8 @@ "[true, \"always-prefix\"]", "[true, \"never-prefix\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": true }, { "ruleName": "jsdoc-format", @@ -194,7 +260,8 @@ "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "label-position", @@ -206,7 +273,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "linebreak-style", @@ -223,7 +291,32 @@ "[true, \"LF\"]", "[true, \"CRLF\"]" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false + }, + { + "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", + "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": { + "type": "array", + "items": [ + { + "type": "number", + "minimum": 1 + } + ], + "additionalItems": false, + "minLength": 1, + "maxLength": 2 + }, + "optionExamples": [ + "[true, 1]", + "[true, 5]" + ], + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "max-file-line-count", @@ -237,7 +330,8 @@ "optionExamples": [ "[true, 300]" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "max-line-length", @@ -251,7 +345,8 @@ "optionExamples": [ "[true, 120]" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "member-access", @@ -274,7 +369,8 @@ "true", "[true, \"check-accessor\"]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "member-ordering", @@ -324,7 +420,8 @@ "optionExamples": [ "[true, { \"order\": \"fields-first\" }]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "new-parens", @@ -335,7 +432,8 @@ "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "no-angle-bracket-type-assertion", @@ -346,7 +444,8 @@ "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": true }, { "ruleName": "no-any", @@ -357,7 +456,8 @@ "optionExamples": [ "true" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "no-arg", @@ -368,7 +468,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-bitwise", @@ -380,7 +481,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-conditional-assignment", @@ -392,18 +494,24 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-consecutive-blank-lines", - "description": "Disallows more than one blank line in a row.", + "description": "Disallows one or more blank lines in a row.", "rationale": "Helps maintain a readable style in your codebase.", - "optionsDescription": "Not configurable.", - "options": {}, + "optionsDescription": "\nAn optional number of maximum allowed sequential blanks can be specified. If no value\nis provided, a default of $(Rule.DEFAULT_ALLOWED_BLANKS) will be used.", + "options": { + "type": "number", + "minimum": "$(Rule.MINIMUM_ALLOWED_BLANKS)" + }, "optionExamples": [ - "true" + "true", + "[true, 2]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "no-console", @@ -419,7 +527,8 @@ "optionExamples": [ "[true, \"log\", \"error\"]" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-construct", @@ -431,7 +540,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-debugger", @@ -442,7 +552,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-default-export", @@ -454,7 +565,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "no-duplicate-variable", @@ -466,7 +578,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-empty", @@ -478,7 +591,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-eval", @@ -489,7 +603,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-for-in-array", @@ -501,7 +616,8 @@ "true" ], "requiresTypeInfo": true, - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-inferrable-types", @@ -523,7 +639,8 @@ "true", "[true, \"ignore-params\"]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "no-internal-module", @@ -534,7 +651,8 @@ "optionExamples": [ "true" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "no-invalid-this", @@ -556,7 +674,8 @@ "true", "[true, \"check-function-in-method\"]" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-mergeable-namespace", @@ -566,7 +685,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": true }, { "ruleName": "no-namespace", @@ -589,7 +709,8 @@ "true", "[true, \"allow-declarations\"]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "no-null-keyword", @@ -600,18 +721,20 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-parameter-properties", - "description": "Disallows parameter properties.", + "description": "Disallows parameter properties in class constructors.", "rationale": "\nParameter properties can be confusing to those new to TS as they are less explicit\nthan other ways of declaring and initializing class members.", "optionsDescription": "Not configurable.", "options": null, "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": true }, { "ruleName": "no-reference", @@ -622,7 +745,8 @@ "optionExamples": [ "true" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": false }, { "ruleName": "no-require-imports", @@ -633,7 +757,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "no-shadowed-variable", @@ -644,7 +769,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-string-literal", @@ -655,7 +781,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-switch-case-fall-through", @@ -667,7 +794,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-trailing-whitespace", @@ -678,18 +806,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" - }, - { - "ruleName": "no-unreachable", - "description": "Disallows unreachable code after `break`, `catch`, `throw`, and `return` statements.", - "rationale": "Unreachable code is often indication of a logic error.", - "optionsDescription": "Not configurable.", - "options": null, - "optionExamples": [ - "true" - ], - "type": "functionality" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "no-unsafe-finally", @@ -701,7 +819,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-unused-expression", @@ -713,7 +832,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-unused-new", @@ -725,10 +845,12 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-unused-variable", + "deprecationMessage": "Use the 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": { @@ -760,7 +882,8 @@ "[true, \"react\"]", "[true, {\"ignore-pattern\": \"^_\"}]" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": true }, { "ruleName": "no-use-before-declare", @@ -771,7 +894,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-var-keyword", @@ -782,7 +906,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "no-var-requires", @@ -793,34 +918,40 @@ "optionExamples": [ "true" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "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\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": [ "always", - "as-needed" + "as-needed", + "consistent", + "consistent-as-needed" ] }, "optionExamples": [ "[true, \"as-needed\"]", "[true, \"always\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "object-literal-shorthand", "description": "Enforces use of ES6 object literal shorthand when possible.", + "optionsDescription": "Not configurable.", "options": null, "optionExamples": [ "true" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "object-literal-sort-keys", @@ -831,7 +962,8 @@ "optionExamples": [ "true" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "one-line", @@ -855,7 +987,8 @@ "optionExamples": [ "[true, \"check-catch\", \"check-finally\", \"check-else\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "one-variable-per-declaration", @@ -876,7 +1009,8 @@ "true", "[true, \"ignore-for-loop\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "only-arrow-functions", @@ -898,13 +1032,14 @@ "true", "[true, \"allow-declarations\"]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": false }, { "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\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\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* `\"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": { @@ -913,7 +1048,8 @@ "enum": [ "case-insensitive", "lowercase-first", - "lowercase-last" + "lowercase-last", + "any" ] }, "named-imports-order": { @@ -921,7 +1057,8 @@ "enum": [ "case-insensitive", "lowercase-first", - "lowercase-last" + "lowercase-last", + "any" ] } }, @@ -931,7 +1068,20 @@ "true", "[true, {\"import-sources-order\": \"lowercase-last\", \"named-imports-order\": \"lowercase-first\"}]" ], - "type": "style" + "type": "style", + "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.", + "rationale": "A for(... of ...) loop is easier to implement and read when the index is not needed.", + "optionsDescription": "Not configurable.", + "options": null, + "optionExamples": [ + "true" + ], + "type": "typescript", + "typescriptOnly": false }, { "ruleName": "quotemark", @@ -956,7 +1106,8 @@ "[true, \"single\", \"avoid-escape\"]", "[true, \"single\", \"jsx-double\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "radix", @@ -967,7 +1118,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "restrict-plus-operands", @@ -978,12 +1130,13 @@ "true" ], "type": "functionality", + "typescriptOnly": false, "requiresTypeInfo": true }, { "ruleName": "semicolon", "description": "Enforces consistent semicolon usage at the end of every statement.", - "optionsDescription": "\nOne of the following arguments must be provided:\n\n* `\"always\"` enforces semicolons at the end of every statement.\n* `\"never\"` disallows semicolons at the end of every statement except for when they are necessary.\n\nThe following arguments may be optionaly provided:\n* `\"ignore-interfaces\"` skips checking semicolons at the end of interface members.", + "optionsDescription": "\nOne of the following arguments must be provided:\n\n* `\"always\"` enforces semicolons at the end of every statement.\n* `\"never\"` disallows semicolons at the end of every statement except for when they are necessary.\n\nThe following arguments may be optionaly provided:\n* `\"ignore-interfaces\"` skips checking semicolons at the end of interface members.\n* `\"ignore-bound-class-methods\"` skips checking semicolons at the end of bound class methods.", "options": { "type": "array", "items": [ @@ -1006,9 +1159,11 @@ "optionExamples": [ "[true, \"always\"]", "[true, \"never\"]", - "[true, \"always\", \"ignore-interfaces\"]" + "[true, \"always\", \"ignore-interfaces\"]", + "[true, \"always\", \"ignore-bound-class-methods\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "switch-default", @@ -1018,12 +1173,13 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "trailing-comma", - "description": "Requires or disallows trailing commas in array and object literals, destructuring assignments and named imports.", - "optionsDescription": "\nOne argument which is an object with the keys `multiline` and `singleline`.\nBoth should be set to either `\"always\"` or `\"never\"`.\n\n* `\"multiline\"` checks multi-line object literals.\n* `\"singleline\"` checks single-line object literals.\n\nA array is considered \"multiline\" if its closing bracket is on a line\nafter the last array element. The same general logic is followed for\nobject literals and named import statements.", + "description": "\nRequires or disallows trailing commas in array and object literals, destructuring assignments, function and tuple typings,\nnamed imports and function parameters.", + "optionsDescription": "\nOne argument which is an object with the keys `multiline` and `singleline`.\nBoth should be set to either `\"always\"` or `\"never\"`.\n\n* `\"multiline\"` checks multi-line object literals.\n* `\"singleline\"` checks single-line object literals.\n\nA array is considered \"multiline\" if its closing bracket is on a line\nafter the last array element. The same general logic is followed for\nobject literals, function and tuple typings, named import statements\nand function parameters.", "options": { "type": "object", "properties": { @@ -1047,7 +1203,8 @@ "optionExamples": [ "[true, {\"multiline\": \"always\", \"singleline\": \"never\"}]" ], - "type": "maintainability" + "type": "maintainability", + "typescriptOnly": false }, { "ruleName": "triple-equals", @@ -1070,7 +1227,8 @@ "[true, \"allow-null-check\"]", "[true, \"allow-undefined-check\"]" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "typedef", @@ -1096,7 +1254,8 @@ "optionExamples": [ "[true, \"call-signature\", \"parameter\", \"member-variable-declaration\"]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "typedef-whitespace", @@ -1204,7 +1363,8 @@ "optionExamples": [ "\n[\n true,\n {\n \"call-signature\": \"nospace\",\n \"index-signature\": \"nospace\",\n \"parameter\": \"nospace\",\n \"property-declaration\": \"nospace\",\n \"variable-declaration\": \"nospace\"\n },\n {\n \"call-signature\": \"onespace\",\n \"index-signature\": \"onespace\",\n \"parameter\": \"onespace\",\n \"property-declaration\": \"onespace\",\n \"variable-declaration\": \"onespace\"\n }\n]" ], - "type": "typescript" + "type": "typescript", + "typescriptOnly": true }, { "ruleName": "use-isnan", @@ -1215,7 +1375,8 @@ "optionExamples": [ "true" ], - "type": "functionality" + "type": "functionality", + "typescriptOnly": false }, { "ruleName": "variable-name", @@ -1239,7 +1400,8 @@ "optionExamples": [ "[true, \"ban-keywords\", \"check-format\", \"allow-leading-underscore\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false }, { "ruleName": "whitespace", @@ -1266,6 +1428,7 @@ "optionExamples": [ "[true, \"check-branch\", \"check-operator\", \"check-typecast\"]" ], - "type": "style" + "type": "style", + "typescriptOnly": false } -] +] \ No newline at end of file diff --git a/docs/_posts/2016-11-17-new-for-4.0.md b/docs/_posts/2016-11-17-new-for-4.0.md new file mode 100644 index 00000000000..29a1ca08595 --- /dev/null +++ b/docs/_posts/2016-11-17-new-for-4.0.md @@ -0,0 +1,83 @@ +--- +layout: post +title: "TSLint 4.0 Released" +date: 2016-11-17 15:19:00 +--- + +TSLint 4.0 has been released! With this release comes a few exciting [changes][0]. Some of the highlights: + +* **Fixers**. Do you dread turning on a new rule because of all of the new errors? For some of the most common issues, we'll fix them for you. To use this feature, run `tslint` with the `--fix` option. Rules that support the `--fix` feature: + * [array-type][2] + * [arrow-parens][3] + * [no-unused-variable][4] (for imports) + * [no-var-keyword][5] + * [ordered-imports][6] + * [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: + * 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]** + * [adjacent-overload-signatures][13] + * [array-type][14] + * [arrow-parens][15] + * [max-classes-per-file][16] + * [no-unsafe-finally][17] + * [object-literal-key-quotes][18] (as needed) + * [object-literal-shorthand][19] + * [only-arrow-functions][20] + * [ordered-imports][21] + * [prefer-for-of][22] + +* **Other rules you might find handy**: + * [completed-docs][23] + * [cyclomatic-complexity][24] + +--- + +## Create your own fixer ## +To create your own fixer, instantiate a `Fix` object and pass it in as an argument to `addFailure`. + +This snippet updates the [sample custom rule][25] by adding a fixer which 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]); + +this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING, fix)); +``` + +[0]: https://github.com/palantir/tslint/releases +[1]: https://github.com/palantir/tslint/blob/master/CHANGELOG.md +[2]: {{ site.baseurl }}/rules/array-type +[3]: {{ site.baseurl }}/rules/arrow-parens +[4]: {{ site.baseurl }}/rules/no-unused-variable +[5]: {{ site.baseurl }}/rules/no-var-keyword +[6]: {{ site.baseurl }}/rules/ordered-imports +[7]: {{ site.baseurl }}/rules/semicolon +[8]: {{ site.baseurl }}/rules/trailing-comma +[9]: https://raw.githubusercontent.com/palantir/tslint/master/src/configs/recommended.ts +[10]: {{ site.baseurl }}/usage/third-party-tools/ +[11]: https://github.com/palantir/tslint/pull/1720 +[12]: https://github.com/palantir/tslint/pull/1717/files#diff-6e3940e8ec3d59837c4435f32975ed2c +[13]: {{ site.baseurl }}/rules/adjacent-overload-signatures +[14]: {{ site.baseurl }}/rules/array-type +[15]: {{ site.baseurl }}/rules/arrow-parens +[16]: {{ site.baseurl }}/rules/max-classes-per-file +[17]: {{ site.baseurl }}/rules/no-unsafe-finally +[18]: {{ site.baseurl }}/rules/object-literal-key-quotes +[19]: {{ site.baseurl }}/rules/object-literal-shorthand +[20]: {{ site.baseurl }}/rules/only-arrow-functions +[21]: {{ site.baseurl }}/rules/ordered-imports +[22]: {{ site.baseurl }}/rules/prefer-for-of +[23]: {{ site.baseurl }}/rules/completed-docs +[24]: {{ site.baseurl }}/rules/cyclomatic-complexity +[25]: {{ site.baseurl }}/develop/custom-rules diff --git a/docs/develop/contributing/index.md b/docs/develop/contributing/index.md index 56586310848..f78a059839f 100644 --- a/docs/develop/contributing/index.md +++ b/docs/develop/contributing/index.md @@ -4,12 +4,13 @@ title: Contributing permalink: /develop/contributing/ --- -To develop TSLint simply clone the repository, install dependencies and run grunt: +To develop TSLint simply clone the repository and install dependencies: ```bash -git clone git@github.com:palantir/tslint.git +git clone git@github.com:palantir/tslint.git --config core.autocrlf=input --config core.eol=lf npm install -grunt +npm run compile +npm run test ``` #### `next` branch @@ -17,4 +18,4 @@ grunt The [`next` branch of the TSLint repo](https://github.com/palantir/tslint/tree/next) tracks the latest TypeScript compiler as a `devDependency`. This allows you to develop the linter and its rules against the latest features of the language. Releases from this branch are published to npm with the `next` dist-tag, so you can get the latest dev -version of TSLint via `npm install tslint@next`. \ No newline at end of file +version of TSLint via `npm install tslint@next`. diff --git a/docs/develop/custom-rules/index.md b/docs/develop/custom-rules/index.md index b8a39ee1c39..c9d45c7308f 100644 --- a/docs/develop/custom-rules/index.md +++ b/docs/develop/custom-rules/index.md @@ -3,20 +3,21 @@ title: Custom Rules layout: page permalink: "/develop/custom-rules/" --- -TSLint ships with a set of core rules that can be configured. However, users are also enabled to write their own rules, which allows them to enforce specific behavior not covered by the core of TSLint. TSLint's internal rules are itself written to be pluggable, so adding a new rule is as simple as creating a new rule file named by convention. New rules can be written in either TypeScript or Javascript; if written in TypeScript, the code must be compiled to Javascript before registering them with TSLint. - -__Important conventions__: Rule identifiers are always kebab-cased. Their implementation files are always `camelCasedRule.ts` and *must* contain the suffix `Rule`. +TSLint ships with a set of core rules that can be configured. However, users are also allowed to write their own rules, which allows them to enforce specific behavior not covered by the core of TSLint. TSLint's internal rules are itself written to be pluggable, so adding a new rule is as simple as creating a new rule file named by convention. New rules can be written in either TypeScript or JavaScript; if written in TypeScript, the code must be compiled to JavaScript before invoking TSLint. 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. -Now, let us first write the rule in TypeScript. A few things to note: +__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`. -- We import `tslint/lib/lint` to get the whole `Lint` namespace instead of just the `Linter` class. -- The exported class must always be named `Rule` and extend from `Lint.Rules.AbstractRule`. +Now, let us first write the rule in TypeScript: -```ts +```typescript import * as ts from "typescript"; -import * as Lint from "tslint/lib/lint"; +import * as Lint from "tslint"; export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "import statement forbidden"; @@ -50,9 +51,24 @@ Then, if using the CLI, provide the directory that contains this rule as an opti Finally, add a line to your [`tslint.json` config file][0] for each of your custom rules. +--- + +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 failure at the current position + this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING, fix)); +``` +--- Final notes: - Core rules cannot be overwritten with a custom implementation. - Custom rules can also take in options just like core rules (retrieved via `this.getOptions()`). -[0]: {{site.baseurl | append: "/usage/tslint-json/"}} \ No newline at end of file +[0]: {{site.baseurl | append: "/usage/tslint-json/"}} diff --git a/docs/develop/docs/index.md b/docs/develop/docs/index.md index e586b66fa05..6b4be4412b9 100644 --- a/docs/develop/docs/index.md +++ b/docs/develop/docs/index.md @@ -9,10 +9,10 @@ It is maintained in the [`/docs` directory][2] of TSLint. To contribute to the docs, whether it be better styling, functionality, or content, just create a PR as you would for any code contribution. #### Updating Rule Documentation #### -The [documentation for rules][3] is automatically generated from the metadata supplied by each rule in its corresponding `.tsx` file. +The [documentation for rules][3] is automatically generated from the metadata supplied by each rule in its corresponding `.ts` file. If you'd like to help improve documentation for them, simply file a PR improving a rule's metadata and a project collaborator will take care of regenerating the docs site once your PR is merged. -Running the `grunt docs` command will regenerate the rules docs based off of the metadata provided in the code. This is normally done each release so that the public docs site is up to date with the latest release. +Running the `npm run docs` command will regenerate the rules docs based off of the metadata provided in the code. This is normally done each release so that the public docs site is up to date with the latest release. #### Creating New Pages #### To create a new page, follow the pattern of existing pages. You'll also need to add appropriate metadata in the `_data/*_sidebar.json` data file if you want it to show up in a sidebar. diff --git a/docs/formatters/applyFixes/index.html b/docs/formatters/applyFixes/index.html deleted file mode 100644 index b860632660b..00000000000 --- a/docs/formatters/applyFixes/index.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -formatterName: applyFixes -description: Automatically fixes lint failures. -descriptionDetails: |- - - Modifies source files and applies fixes for lint failures where possible. Changes - should be tested as not all fixes preserve semantics. -sample: |- - - All done. Remember to test the changes, as not all fixes preserve semantics. -consumer: machine -layout: formatter -title: 'Formatter: applyFixes' ---- \ No newline at end of file diff --git a/docs/rules/adjacent-overload-signatures/index.html b/docs/rules/adjacent-overload-signatures/index.html index d518c87ec36..3506700140c 100644 --- a/docs/rules/adjacent-overload-signatures/index.html +++ b/docs/rules/adjacent-overload-signatures/index.html @@ -5,8 +5,10 @@ options: null optionExamples: - 'true' +rationale: Improves readability and organization by grouping naturally related items together. type: typescript -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: adjacent-overload-signatures' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/align/index.html b/docs/rules/align/index.html index 966ce1556f1..f42a1421e39 100644 --- a/docs/rules/align/index.html +++ b/docs/rules/align/index.html @@ -22,6 +22,9 @@ optionExamples: - '[true, "parameters", "statements"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: align' optionsJSON: |- { "type": "array", @@ -36,6 +39,4 @@ "minLength": 1, "maxLength": 3 } -layout: rule -title: 'Rule: align' --- \ No newline at end of file diff --git a/docs/rules/array-type/index.html b/docs/rules/array-type/index.html new file mode 100644 index 00000000000..5a5cfe27139 --- /dev/null +++ b/docs/rules/array-type/index.html @@ -0,0 +1,34 @@ +--- +ruleName: array-type +description: 'Requires using either ''T[]'' or ''Array'' for arrays.' +optionsDescription: |- + + One of the following arguments must be provided: + + * `"array"` enforces use of `T[]` for all types T. + * `"generic"` enforces use of `Array` for all types T. + * `"array-simple"` enforces use of `T[]` if `T` is a simple type (primitive or type reference). +options: + type: string + enum: + - array + - generic + - array-simple +optionExamples: + - '[true, array]' + - '[true, generic]' + - '[true, array-simple]' +type: style +typescriptOnly: true +layout: rule +title: 'Rule: array-type' +optionsJSON: |- + { + "type": "string", + "enum": [ + "array", + "generic", + "array-simple" + ] + } +--- \ No newline at end of file diff --git a/docs/rules/arrow-parens/index.html b/docs/rules/arrow-parens/index.html index 0d356c11994..e77c46b1d1a 100644 --- a/docs/rules/arrow-parens/index.html +++ b/docs/rules/arrow-parens/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: arrow-parens' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/ban/index.html b/docs/rules/ban/index.html index 279cc53ec8a..f3005766579 100644 --- a/docs/rules/ban/index.html +++ b/docs/rules/ban/index.html @@ -1,37 +1,36 @@ --- ruleName: ban -description: Bans the use of specific functions. -descriptionDetails: 'At this time, there is no way to disable global methods with this rule.' -optionsDescription: 'A list of `[''object'', ''method'', ''optional explanation here'']` which ban `object.method()`.' +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()` + or respectively `globalMethod()`. options: type: list listType: type: array - arrayMembers: - - type: string - - type: string - - type: string + items: + type: string + minLength: 1 + maxLength: 3 optionExamples: - - '[true, ["someObject", "someFunction"], ["someObject", "otherFunction", "Optional explanation"]]' + - |- + [true, ["someGlobalMethod"], ["someObject", "someFunction"], + ["someObject", "otherFunction", "Optional explanation"]] type: functionality +typescriptOnly: false +layout: rule +title: 'Rule: ban' optionsJSON: |- { "type": "list", "listType": { "type": "array", - "arrayMembers": [ - { - "type": "string" - }, - { - "type": "string" - }, - { - "type": "string" - } - ] + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 3 } } -layout: rule -title: 'Rule: ban' --- \ No newline at end of file diff --git a/docs/rules/class-name/index.html b/docs/rules/class-name/index.html index 43fdc02f882..0ae19d6e8be 100644 --- a/docs/rules/class-name/index.html +++ b/docs/rules/class-name/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: class-name' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/comment-format/index.html b/docs/rules/comment-format/index.html index 1e170990e20..cbc4b7813a0 100644 --- a/docs/rules/comment-format/index.html +++ b/docs/rules/comment-format/index.html @@ -23,6 +23,9 @@ optionExamples: - '[true, "check-space", "check-lowercase"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: comment-format' optionsJSON: |- { "type": "array", @@ -37,6 +40,4 @@ "minLength": 1, "maxLength": 3 } -layout: rule -title: 'Rule: comment-format' --- \ No newline at end of file diff --git a/docs/rules/completed-docs/index.html b/docs/rules/completed-docs/index.html new file mode 100644 index 00000000000..128a663d3db --- /dev/null +++ b/docs/rules/completed-docs/index.html @@ -0,0 +1,38 @@ +--- +ruleName: completed-docs +description: Enforces documentation for important items be filled out. +optionsDescription: |- + + Either `true` to enable for all, or any of + `["classes", "functions", "methods", "properties"] + to choose individual ones.` +options: + type: array + items: + type: string + enum: + - classes + - functions + - methods + - properties +optionExamples: + - 'true' + - '[true, ["classes", "functions"]' +type: style +typescriptOnly: false +layout: rule +title: 'Rule: completed-docs' +optionsJSON: |- + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "classes", + "functions", + "methods", + "properties" + ] + } + } +--- \ No newline at end of file diff --git a/docs/rules/curly/index.html b/docs/rules/curly/index.html index 1ce4a8eedac..5e9075da7fe 100644 --- a/docs/rules/curly/index.html +++ b/docs/rules/curly/index.html @@ -17,7 +17,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: curly' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/cyclomatic-complexity/index.html b/docs/rules/cyclomatic-complexity/index.html new file mode 100644 index 00000000000..4eddc29446d --- /dev/null +++ b/docs/rules/cyclomatic-complexity/index.html @@ -0,0 +1,39 @@ +--- +ruleName: cyclomatic-complexity +description: Enforces a threshold of cyclomatic complexity. +descriptionDetails: |- + + Cyclomatic complexity is assessed for each function of any type. A starting value of 1 + 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: + * `catch` + * `if` and `? :` + * `||` and `&&` due to short-circuit evaluation + * `for`, `for in` and `for of` loops + * `while` and `do while` loops +rationale: |- + + Cyclomatic complexity is a code metric which indicates the level of complexity in a + function. High cyclomatic complexity indicates confusing code which may be prone to + errors or difficult to modify. +optionsDescription: |- + + An optional upper limit for cyclomatic complexity can be specified. If no limit option + is provided a default value of $(Rule.DEFAULT_THRESHOLD) will be used. +options: + type: number + minimum: $(Rule.MINIMUM_THRESHOLD) +optionExamples: + - 'true' + - '[true, 20]' +type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: cyclomatic-complexity' +optionsJSON: |- + { + "type": "number", + "minimum": "$(Rule.MINIMUM_THRESHOLD)" + } +--- \ No newline at end of file diff --git a/docs/rules/eofline/index.html b/docs/rules/eofline/index.html index 81ddde9865e..985298840d8 100644 --- a/docs/rules/eofline/index.html +++ b/docs/rules/eofline/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: eofline' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/file-header/index.html b/docs/rules/file-header/index.html index 188c64955fc..7cc981b6bb8 100644 --- a/docs/rules/file-header/index.html +++ b/docs/rules/file-header/index.html @@ -5,12 +5,13 @@ options: type: string optionExamples: - - '"true", "Copyright \d{4}"' + - '[true, "Copyright \\d{4}"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: file-header' optionsJSON: |- { "type": "string" } -layout: rule -title: 'Rule: file-header' --- \ No newline at end of file diff --git a/docs/rules/forin/index.html b/docs/rules/forin/index.html index 9ec8b7baa00..e41a69c1068 100644 --- a/docs/rules/forin/index.html +++ b/docs/rules/forin/index.html @@ -18,7 +18,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: forin' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/indent/index.html b/docs/rules/indent/index.html index 5962ea11c0a..28d55c314b5 100644 --- a/docs/rules/indent/index.html +++ b/docs/rules/indent/index.html @@ -19,6 +19,9 @@ optionExamples: - '[true, "spaces"]' type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: indent' optionsJSON: |- { "type": "string", @@ -27,6 +30,4 @@ "spaces" ] } -layout: rule -title: 'Rule: indent' --- \ No newline at end of file diff --git a/docs/rules/interface-name/index.html b/docs/rules/interface-name/index.html index 81a93b61162..91375f2a0ec 100644 --- a/docs/rules/interface-name/index.html +++ b/docs/rules/interface-name/index.html @@ -17,6 +17,9 @@ - '[true, "always-prefix"]' - '[true, "never-prefix"]' type: style +typescriptOnly: true +layout: rule +title: 'Rule: interface-name' optionsJSON: |- { "type": "string", @@ -25,6 +28,4 @@ "never-prefix" ] } -layout: rule -title: 'Rule: interface-name' --- \ No newline at end of file diff --git a/docs/rules/jsdoc-format/index.html b/docs/rules/jsdoc-format/index.html index de7cd5e2731..269ceab1b0a 100644 --- a/docs/rules/jsdoc-format/index.html +++ b/docs/rules/jsdoc-format/index.html @@ -15,7 +15,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: jsdoc-format' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/label-position/index.html b/docs/rules/label-position/index.html index bd706278995..d82ae64eab8 100644 --- a/docs/rules/label-position/index.html +++ b/docs/rules/label-position/index.html @@ -12,7 +12,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: label-position' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/linebreak-style/index.html b/docs/rules/linebreak-style/index.html index f5c695c8072..363c990858e 100644 --- a/docs/rules/linebreak-style/index.html +++ b/docs/rules/linebreak-style/index.html @@ -16,6 +16,9 @@ - '[true, "LF"]' - '[true, "CRLF"]' type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: linebreak-style' optionsJSON: |- { "type": "string", @@ -24,6 +27,4 @@ "CRLF" ] } -layout: rule -title: 'Rule: linebreak-style' --- \ No newline at end of file diff --git a/docs/rules/max-classes-per-file/index.html b/docs/rules/max-classes-per-file/index.html new file mode 100644 index 00000000000..1827aab9dc8 --- /dev/null +++ b/docs/rules/max-classes-per-file/index.html @@ -0,0 +1,41 @@ +--- +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 +rationale: |- + + Ensures that files have a single responsibility so that that classes each exist in their own files +optionsDescription: |- + + The one required argument is an integer indicating the maximum number of classes that can appear in a file. +options: + type: array + items: + - type: number + minimum: 1 + additionalItems: false + minLength: 1 + maxLength: 2 +optionExamples: + - '[true, 1]' + - '[true, 5]' +type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: max-classes-per-file' +optionsJSON: |- + { + "type": "array", + "items": [ + { + "type": "number", + "minimum": 1 + } + ], + "additionalItems": false, + "minLength": 1, + "maxLength": 2 + } +--- \ No newline at end of file diff --git a/docs/rules/max-file-line-count/index.html b/docs/rules/max-file-line-count/index.html index bdb39e39c5d..88556799827 100644 --- a/docs/rules/max-file-line-count/index.html +++ b/docs/rules/max-file-line-count/index.html @@ -12,11 +12,12 @@ optionExamples: - '[true, 300]' type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: max-file-line-count' optionsJSON: |- { "type": "number", "minimum": "1" } -layout: rule -title: 'Rule: max-file-line-count' --- \ No newline at end of file diff --git a/docs/rules/max-line-length/index.html b/docs/rules/max-line-length/index.html index e11807c5528..fdd451fce6a 100644 --- a/docs/rules/max-line-length/index.html +++ b/docs/rules/max-line-length/index.html @@ -13,11 +13,12 @@ optionExamples: - '[true, 120]' type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: max-line-length' optionsJSON: |- { "type": "number", "minimum": "1" } -layout: rule -title: 'Rule: max-line-length' --- \ No newline at end of file diff --git a/docs/rules/member-access/index.html b/docs/rules/member-access/index.html index 006c20c4dfd..ae5c4991013 100644 --- a/docs/rules/member-access/index.html +++ b/docs/rules/member-access/index.html @@ -21,6 +21,9 @@ - 'true' - '[true, "check-accessor"]' type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: member-access' optionsJSON: |- { "type": "array", @@ -34,6 +37,4 @@ "minLength": 0, "maxLength": 2 } -layout: rule -title: 'Rule: member-access' --- \ No newline at end of file diff --git a/docs/rules/member-ordering/index.html b/docs/rules/member-ordering/index.html index 3ea91700dbe..295ced8cb52 100644 --- a/docs/rules/member-ordering/index.html +++ b/docs/rules/member-ordering/index.html @@ -60,6 +60,9 @@ optionExamples: - '[true, { "order": "fields-first" }]' type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: member-ordering' optionsJSON: |- { "type": "object", @@ -101,6 +104,4 @@ }, "additionalProperties": false } -layout: rule -title: 'Rule: member-ordering' --- \ No newline at end of file diff --git a/docs/rules/new-parens/index.html b/docs/rules/new-parens/index.html index d1cdf11ab25..58aa3916f69 100644 --- a/docs/rules/new-parens/index.html +++ b/docs/rules/new-parens/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: new-parens' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-angle-bracket-type-assertion/index.html b/docs/rules/no-angle-bracket-type-assertion/index.html index 36bf52bb044..7353488bdba 100644 --- a/docs/rules/no-angle-bracket-type-assertion/index.html +++ b/docs/rules/no-angle-bracket-type-assertion/index.html @@ -11,7 +11,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: no-angle-bracket-type-assertion' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-any/index.html b/docs/rules/no-any/index.html index 08a9394329b..9e96a7e560a 100644 --- a/docs/rules/no-any/index.html +++ b/docs/rules/no-any/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: typescript -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: no-any' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-arg/index.html b/docs/rules/no-arg/index.html index 388c3194d36..a49b3401ff5 100644 --- a/docs/rules/no-arg/index.html +++ b/docs/rules/no-arg/index.html @@ -11,7 +11,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-arg' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-bitwise/index.html b/docs/rules/no-bitwise/index.html index e7dbe9f6bf5..9c83ca93d89 100644 --- a/docs/rules/no-bitwise/index.html +++ b/docs/rules/no-bitwise/index.html @@ -17,7 +17,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-bitwise' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-conditional-assignment/index.html b/docs/rules/no-conditional-assignment/index.html index c38f9119579..b7bdc4041ef 100644 --- a/docs/rules/no-conditional-assignment/index.html +++ b/docs/rules/no-conditional-assignment/index.html @@ -12,7 +12,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-conditional-assignment' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-consecutive-blank-lines/index.html b/docs/rules/no-consecutive-blank-lines/index.html index 1e9ec855f7b..180294b281f 100644 --- a/docs/rules/no-consecutive-blank-lines/index.html +++ b/docs/rules/no-consecutive-blank-lines/index.html @@ -1,13 +1,24 @@ --- ruleName: no-consecutive-blank-lines -description: Disallows more than one blank line in a row. +description: Disallows one or more blank lines in a row. rationale: Helps maintain a readable style in your codebase. -optionsDescription: Not configurable. -options: {} +optionsDescription: |- + + An optional number of maximum allowed sequential blanks can be specified. If no value + is provided, a default of $(Rule.DEFAULT_ALLOWED_BLANKS) will be used. +options: + type: number + minimum: $(Rule.MINIMUM_ALLOWED_BLANKS) optionExamples: - 'true' + - '[true, 2]' type: style -optionsJSON: '{}' +typescriptOnly: false layout: rule title: 'Rule: no-consecutive-blank-lines' +optionsJSON: |- + { + "type": "number", + "minimum": "$(Rule.MINIMUM_ALLOWED_BLANKS)" + } --- \ No newline at end of file diff --git a/docs/rules/no-console/index.html b/docs/rules/no-console/index.html index d8066eeee0d..3a04d5c29f2 100644 --- a/docs/rules/no-console/index.html +++ b/docs/rules/no-console/index.html @@ -10,6 +10,9 @@ optionExamples: - '[true, "log", "error"]' type: functionality +typescriptOnly: false +layout: rule +title: 'Rule: no-console' optionsJSON: |- { "type": "array", @@ -17,6 +20,4 @@ "type": "string" } } -layout: rule -title: 'Rule: no-console' --- \ No newline at end of file diff --git a/docs/rules/no-construct/index.html b/docs/rules/no-construct/index.html index a1f54fb6aba..d7f937db798 100644 --- a/docs/rules/no-construct/index.html +++ b/docs/rules/no-construct/index.html @@ -12,7 +12,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-construct' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-debugger/index.html b/docs/rules/no-debugger/index.html index 409c7ae3534..a3133012e5f 100644 --- a/docs/rules/no-debugger/index.html +++ b/docs/rules/no-debugger/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-debugger' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-default-export/index.html b/docs/rules/no-default-export/index.html index ab17c9c46b6..8e7a279a2a7 100644 --- a/docs/rules/no-default-export/index.html +++ b/docs/rules/no-default-export/index.html @@ -12,7 +12,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-default-export' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-duplicate-variable/index.html b/docs/rules/no-duplicate-variable/index.html index 26dbce48ecb..3dee824d2ce 100644 --- a/docs/rules/no-duplicate-variable/index.html +++ b/docs/rules/no-duplicate-variable/index.html @@ -14,7 +14,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-duplicate-variable' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-empty/index.html b/docs/rules/no-empty/index.html index 46a17a83dad..5fe4cc64ff0 100644 --- a/docs/rules/no-empty/index.html +++ b/docs/rules/no-empty/index.html @@ -8,7 +8,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-empty' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-eval/index.html b/docs/rules/no-eval/index.html index 30feb3c6ba6..63f08fba017 100644 --- a/docs/rules/no-eval/index.html +++ b/docs/rules/no-eval/index.html @@ -11,7 +11,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-eval' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-for-in-array/index.html b/docs/rules/no-for-in-array/index.html index ac33d240bb1..781e895234f 100644 --- a/docs/rules/no-for-in-array/index.html +++ b/docs/rules/no-for-in-array/index.html @@ -21,7 +21,8 @@ - 'true' requiresTypeInfo: true type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-for-in-array' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-inferrable-types/index.html b/docs/rules/no-inferrable-types/index.html index 1a01bde98cc..4518dc603e5 100644 --- a/docs/rules/no-inferrable-types/index.html +++ b/docs/rules/no-inferrable-types/index.html @@ -20,6 +20,9 @@ - 'true' - '[true, "ignore-params"]' type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: no-inferrable-types' optionsJSON: |- { "type": "array", @@ -32,6 +35,4 @@ "minLength": 0, "maxLength": 1 } -layout: rule -title: 'Rule: no-inferrable-types' --- \ No newline at end of file diff --git a/docs/rules/no-internal-module/index.html b/docs/rules/no-internal-module/index.html index 95a3965c86f..5092d8ef529 100644 --- a/docs/rules/no-internal-module/index.html +++ b/docs/rules/no-internal-module/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: typescript -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: no-internal-module' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-invalid-this/index.html b/docs/rules/no-invalid-this/index.html index 1126670a835..fb2d3d34ed2 100644 --- a/docs/rules/no-invalid-this/index.html +++ b/docs/rules/no-invalid-this/index.html @@ -19,6 +19,9 @@ - 'true' - '[true, "check-function-in-method"]' type: functionality +typescriptOnly: false +layout: rule +title: 'Rule: no-invalid-this' optionsJSON: |- { "type": "array", @@ -31,6 +34,4 @@ "minLength": 0, "maxLength": 1 } -layout: rule -title: 'Rule: no-invalid-this' --- \ No newline at end of file diff --git a/docs/rules/no-mergeable-namespace/index.html b/docs/rules/no-mergeable-namespace/index.html index be0ab94e3b1..ca24c662893 100644 --- a/docs/rules/no-mergeable-namespace/index.html +++ b/docs/rules/no-mergeable-namespace/index.html @@ -6,7 +6,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: no-mergeable-namespace' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-namespace/index.html b/docs/rules/no-namespace/index.html index fe69cd32512..d29ecbf5262 100644 --- a/docs/rules/no-namespace/index.html +++ b/docs/rules/no-namespace/index.html @@ -23,6 +23,9 @@ - 'true' - '[true, "allow-declarations"]' type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: no-namespace' optionsJSON: |- { "type": "array", @@ -35,6 +38,4 @@ "minLength": 0, "maxLength": 1 } -layout: rule -title: 'Rule: no-namespace' --- \ No newline at end of file diff --git a/docs/rules/no-null-keyword/index.html b/docs/rules/no-null-keyword/index.html index 6bb6bb71e6f..c4c46d69f8d 100644 --- a/docs/rules/no-null-keyword/index.html +++ b/docs/rules/no-null-keyword/index.html @@ -10,7 +10,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-null-keyword' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-parameter-properties/index.html b/docs/rules/no-parameter-properties/index.html index d2fc9e99326..bac7fbb8f6e 100644 --- a/docs/rules/no-parameter-properties/index.html +++ b/docs/rules/no-parameter-properties/index.html @@ -1,6 +1,6 @@ --- -ruleName: no-constructor-vars -description: Disallows parameter properties. +ruleName: no-parameter-properties +description: Disallows parameter properties in class constructors. rationale: |- Parameter properties can be confusing to those new to TS as they are less explicit @@ -10,7 +10,8 @@ optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: true layout: rule -title: 'Rule: no-constructor-vars' +title: 'Rule: no-parameter-properties' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-reference/index.html b/docs/rules/no-reference/index.html index d77be60532e..85ff055965e 100644 --- a/docs/rules/no-reference/index.html +++ b/docs/rules/no-reference/index.html @@ -10,7 +10,8 @@ optionExamples: - 'true' type: typescript -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-reference' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-require-imports/index.html b/docs/rules/no-require-imports/index.html index b17524c8951..0791ef11775 100644 --- a/docs/rules/no-require-imports/index.html +++ b/docs/rules/no-require-imports/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-require-imports' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-shadowed-variable/index.html b/docs/rules/no-shadowed-variable/index.html index d6b60ae4316..c9b41de6420 100644 --- a/docs/rules/no-shadowed-variable/index.html +++ b/docs/rules/no-shadowed-variable/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-shadowed-variable' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-string-literal/index.html b/docs/rules/no-string-literal/index.html index 9db77bb7810..c4472e65b53 100644 --- a/docs/rules/no-string-literal/index.html +++ b/docs/rules/no-string-literal/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-string-literal' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-switch-case-fall-through/index.html b/docs/rules/no-switch-case-fall-through/index.html index 52ddb72588a..e6ff8459f6d 100644 --- a/docs/rules/no-switch-case-fall-through/index.html +++ b/docs/rules/no-switch-case-fall-through/index.html @@ -33,7 +33,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-switch-case-fall-through' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-trailing-whitespace/index.html b/docs/rules/no-trailing-whitespace/index.html index 6a2291ced17..eef2dc69bf4 100644 --- a/docs/rules/no-trailing-whitespace/index.html +++ b/docs/rules/no-trailing-whitespace/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-trailing-whitespace' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-unsafe-finally/index.html b/docs/rules/no-unsafe-finally/index.html index e630ded10c4..1d6e46900c8 100644 --- a/docs/rules/no-unsafe-finally/index.html +++ b/docs/rules/no-unsafe-finally/index.html @@ -16,7 +16,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-unsafe-finally' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-unused-expression/index.html b/docs/rules/no-unused-expression/index.html index e44c9eb01ef..82075de330a 100644 --- a/docs/rules/no-unused-expression/index.html +++ b/docs/rules/no-unused-expression/index.html @@ -13,7 +13,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-unused-expression' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-unused-new/index.html b/docs/rules/no-unused-new/index.html index f2add294cb5..4508b8fb8c6 100644 --- a/docs/rules/no-unused-new/index.html +++ b/docs/rules/no-unused-new/index.html @@ -13,7 +13,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-unused-new' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-unused-variable/index.html b/docs/rules/no-unused-variable/index.html index b892b4d3518..3487ff0e49d 100644 --- a/docs/rules/no-unused-variable/index.html +++ b/docs/rules/no-unused-variable/index.html @@ -1,5 +1,6 @@ --- ruleName: no-unused-variable +deprecationMessage: Use the compiler options --noUnusedParameters and --noUnusedLocals instead. description: 'Disallows unused imports, variables, functions and private class members.' optionsDescription: |- @@ -33,6 +34,9 @@ - '[true, "react"]' - '[true, {"ignore-pattern": "^_"}]' type: functionality +typescriptOnly: true +layout: rule +title: 'Rule: no-unused-variable' optionsJSON: |- { "type": "array", @@ -59,6 +63,4 @@ "minLength": 0, "maxLength": 3 } -layout: rule -title: 'Rule: no-unused-variable' --- \ No newline at end of file diff --git a/docs/rules/no-use-before-declare/index.html b/docs/rules/no-use-before-declare/index.html index e590931e298..d2c24056758 100644 --- a/docs/rules/no-use-before-declare/index.html +++ b/docs/rules/no-use-before-declare/index.html @@ -10,7 +10,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-use-before-declare' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-var-keyword/index.html b/docs/rules/no-var-keyword/index.html index 5fb0d769b0a..addab8d448c 100644 --- a/docs/rules/no-var-keyword/index.html +++ b/docs/rules/no-var-keyword/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: no-var-keyword' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/no-var-requires/index.html b/docs/rules/no-var-requires/index.html index d0d6dd071b8..ecbbca8fe5a 100644 --- a/docs/rules/no-var-requires/index.html +++ b/docs/rules/no-var-requires/index.html @@ -10,7 +10,8 @@ optionExamples: - 'true' type: typescript -optionsJSON: 'null' +typescriptOnly: true layout: rule title: 'Rule: no-var-requires' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/object-literal-key-quotes/index.html b/docs/rules/object-literal-key-quotes/index.html index 016e463b16f..66b593d1860 100644 --- a/docs/rules/object-literal-key-quotes/index.html +++ b/docs/rules/object-literal-key-quotes/index.html @@ -25,6 +25,9 @@ * `"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 + property names may be quoted. For ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need to be quoted. @@ -33,18 +36,23 @@ enum: - always - as-needed + - consistent + - consistent-as-needed optionExamples: - '[true, "as-needed"]' - '[true, "always"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: object-literal-key-quotes' optionsJSON: |- { "type": "string", "enum": [ "always", - "as-needed" + "as-needed", + "consistent", + "consistent-as-needed" ] } -layout: rule -title: 'Rule: object-literal-key-quotes' --- \ No newline at end of file diff --git a/docs/rules/object-literal-shorthand/index.html b/docs/rules/object-literal-shorthand/index.html index 3917afade21..1a6ca8708e8 100644 --- a/docs/rules/object-literal-shorthand/index.html +++ b/docs/rules/object-literal-shorthand/index.html @@ -1,11 +1,13 @@ --- ruleName: object-literal-shorthand description: Enforces use of ES6 object literal shorthand when possible. +optionsDescription: Not configurable. options: null optionExamples: - 'true' type: style -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: object-literal-shorthand' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/object-literal-sort-keys/index.html b/docs/rules/object-literal-sort-keys/index.html index 74c54296cf7..44dcc1850e8 100644 --- a/docs/rules/object-literal-sort-keys/index.html +++ b/docs/rules/object-literal-sort-keys/index.html @@ -7,7 +7,8 @@ optionExamples: - 'true' type: maintainability -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: object-literal-sort-keys' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/one-line/index.html b/docs/rules/one-line/index.html index ed9bb4b3ba7..646e89b8d56 100644 --- a/docs/rules/one-line/index.html +++ b/docs/rules/one-line/index.html @@ -25,6 +25,9 @@ optionExamples: - '[true, "check-catch", "check-finally", "check-else"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: one-line' optionsJSON: |- { "type": "array", @@ -41,6 +44,4 @@ "minLength": 0, "maxLength": 5 } -layout: rule -title: 'Rule: one-line' --- \ No newline at end of file diff --git a/docs/rules/one-variable-per-declaration/index.html b/docs/rules/one-variable-per-declaration/index.html index f90487ea783..327b8b59f65 100644 --- a/docs/rules/one-variable-per-declaration/index.html +++ b/docs/rules/one-variable-per-declaration/index.html @@ -18,6 +18,9 @@ - 'true' - '[true, "ignore-for-loop"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: one-variable-per-declaration' optionsJSON: |- { "type": "array", @@ -30,6 +33,4 @@ "minLength": 0, "maxLength": 1 } -layout: rule -title: 'Rule: one-variable-per-declaration' --- \ No newline at end of file diff --git a/docs/rules/only-arrow-functions/index.html b/docs/rules/only-arrow-functions/index.html index 7d5adbd6f63..f88c10f5bbb 100644 --- a/docs/rules/only-arrow-functions/index.html +++ b/docs/rules/only-arrow-functions/index.html @@ -20,6 +20,9 @@ - 'true' - '[true, "allow-declarations"]' type: typescript +typescriptOnly: false +layout: rule +title: 'Rule: only-arrow-functions' optionsJSON: |- { "type": "array", @@ -32,6 +35,4 @@ "minLength": 0, "maxLength": 1 } -layout: rule -title: 'Rule: only-arrow-functions' --- \ No newline at end of file diff --git a/docs/rules/ordered-imports/index.html b/docs/rules/ordered-imports/index.html index 64691a23f70..78d7a0a5466 100644 --- a/docs/rules/ordered-imports/index.html +++ b/docs/rules/ordered-imports/index.html @@ -21,6 +21,7 @@ * `"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"`. + * `"any"`: Allow any order. You may set the `"named-imports-order"` option to control the ordering of named imports (the `{A, B, C}` in `import {A, B, C} from "foo"`). @@ -30,6 +31,7 @@ * `"case-insensitive'`: Correct order is `{A, b, C}`. (This is the default.) * `"lowercase-first"`: Correct order is `{b, A, C}`. * `"lowercase-last"`: Correct order is `{A, C, b}`. + * `"any"`: Allow any order. options: @@ -41,17 +43,22 @@ - case-insensitive - lowercase-first - lowercase-last + - any named-imports-order: type: string enum: - case-insensitive - lowercase-first - lowercase-last + - any additionalProperties: false optionExamples: - 'true' - '[true, {"import-sources-order": "lowercase-last", "named-imports-order": "lowercase-first"}]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: ordered-imports' optionsJSON: |- { "type": "object", @@ -61,7 +68,8 @@ "enum": [ "case-insensitive", "lowercase-first", - "lowercase-last" + "lowercase-last", + "any" ] }, "named-imports-order": { @@ -69,12 +77,11 @@ "enum": [ "case-insensitive", "lowercase-first", - "lowercase-last" + "lowercase-last", + "any" ] } }, "additionalProperties": false } -layout: rule -title: 'Rule: ordered-imports' --- \ No newline at end of file diff --git a/docs/rules/prefer-for-of/index.html b/docs/rules/prefer-for-of/index.html new file mode 100644 index 00000000000..182c695e16f --- /dev/null +++ b/docs/rules/prefer-for-of/index.html @@ -0,0 +1,14 @@ +--- +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. +rationale: A for(... of ...) loop is easier to implement and read when the index is not needed. +optionsDescription: Not configurable. +options: null +optionExamples: + - 'true' +type: typescript +typescriptOnly: false +layout: rule +title: 'Rule: prefer-for-of' +optionsJSON: 'null' +--- \ No newline at end of file diff --git a/docs/rules/quotemark/index.html b/docs/rules/quotemark/index.html index 1163f9023f9..19eb51583cd 100644 --- a/docs/rules/quotemark/index.html +++ b/docs/rules/quotemark/index.html @@ -27,6 +27,9 @@ - '[true, "single", "avoid-escape"]' - '[true, "single", "jsx-double"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: quotemark' optionsJSON: |- { "type": "array", @@ -43,6 +46,4 @@ "minLength": 0, "maxLength": 5 } -layout: rule -title: 'Rule: quotemark' --- \ No newline at end of file diff --git a/docs/rules/radix/index.html b/docs/rules/radix/index.html index 33209320a3b..f951cffb6ac 100644 --- a/docs/rules/radix/index.html +++ b/docs/rules/radix/index.html @@ -11,7 +11,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: radix' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/restrict-plus-operands/index.html b/docs/rules/restrict-plus-operands/index.html index 902b433010a..1a20bb14ee8 100644 --- a/docs/rules/restrict-plus-operands/index.html +++ b/docs/rules/restrict-plus-operands/index.html @@ -6,8 +6,9 @@ optionExamples: - 'true' type: functionality +typescriptOnly: false requiresTypeInfo: true -optionsJSON: 'null' layout: rule title: 'Rule: restrict-plus-operands' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/semicolon/index.html b/docs/rules/semicolon/index.html index 5bbb10c22e4..095bfb1ee96 100644 --- a/docs/rules/semicolon/index.html +++ b/docs/rules/semicolon/index.html @@ -10,6 +10,7 @@ The following arguments may be optionaly provided: * `"ignore-interfaces"` skips checking semicolons at the end of interface members. + * `"ignore-bound-class-methods"` skips checking semicolons at the end of bound class methods. options: type: array items: @@ -25,7 +26,11 @@ - '[true, "always"]' - '[true, "never"]' - '[true, "always", "ignore-interfaces"]' + - '[true, "always", "ignore-bound-class-methods"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: semicolon' optionsJSON: |- { "type": "array", @@ -46,6 +51,4 @@ ], "additionalItems": false } -layout: rule -title: 'Rule: semicolon' --- \ No newline at end of file diff --git a/docs/rules/switch-default/index.html b/docs/rules/switch-default/index.html index f6bc874814a..4bfa95371fa 100644 --- a/docs/rules/switch-default/index.html +++ b/docs/rules/switch-default/index.html @@ -6,7 +6,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: switch-default' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/trailing-comma/index.html b/docs/rules/trailing-comma/index.html index ef0150a62c8..c2e2aeeb843 100644 --- a/docs/rules/trailing-comma/index.html +++ b/docs/rules/trailing-comma/index.html @@ -1,6 +1,9 @@ --- ruleName: trailing-comma -description: 'Requires or disallows trailing commas in array and object literals, destructuring assignments and named imports.' +description: |- + + Requires or disallows trailing commas in array and object literals, destructuring assignments, function and tuple typings, + named imports and function parameters. optionsDescription: |- One argument which is an object with the keys `multiline` and `singleline`. @@ -11,7 +14,8 @@ A array is considered "multiline" if its closing bracket is on a line after the last array element. The same general logic is followed for - object literals and named import statements. + object literals, function and tuple typings, named import statements + and function parameters. options: type: object properties: @@ -29,6 +33,9 @@ optionExamples: - '[true, {"multiline": "always", "singleline": "never"}]' type: maintainability +typescriptOnly: false +layout: rule +title: 'Rule: trailing-comma' optionsJSON: |- { "type": "object", @@ -50,6 +57,4 @@ }, "additionalProperties": false } -layout: rule -title: 'Rule: trailing-comma' --- \ No newline at end of file diff --git a/docs/rules/triple-equals/index.html b/docs/rules/triple-equals/index.html index 5ad73c8c86d..1fdfe98e5b8 100644 --- a/docs/rules/triple-equals/index.html +++ b/docs/rules/triple-equals/index.html @@ -21,6 +21,9 @@ - '[true, "allow-null-check"]' - '[true, "allow-undefined-check"]' type: functionality +typescriptOnly: false +layout: rule +title: 'Rule: triple-equals' optionsJSON: |- { "type": "array", @@ -34,6 +37,4 @@ "minLength": 0, "maxLength": 2 } -layout: rule -title: 'Rule: triple-equals' --- \ No newline at end of file diff --git a/docs/rules/typedef-whitespace/index.html b/docs/rules/typedef-whitespace/index.html index db7134d3dfb..8fcd3b63087 100644 --- a/docs/rules/typedef-whitespace/index.html +++ b/docs/rules/typedef-whitespace/index.html @@ -55,6 +55,9 @@ } ] type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: typedef-whitespace' optionsJSON: |- { "type": "array", @@ -154,6 +157,4 @@ ], "additionalItems": false } -layout: rule -title: 'Rule: typedef-whitespace' --- \ No newline at end of file diff --git a/docs/rules/typedef/index.html b/docs/rules/typedef/index.html index 727f9ed08b6..67ca7f977f2 100644 --- a/docs/rules/typedef/index.html +++ b/docs/rules/typedef/index.html @@ -29,6 +29,9 @@ optionExamples: - '[true, "call-signature", "parameter", "member-variable-declaration"]' type: typescript +typescriptOnly: true +layout: rule +title: 'Rule: typedef' optionsJSON: |- { "type": "array", @@ -47,6 +50,4 @@ "minLength": 0, "maxLength": 7 } -layout: rule -title: 'Rule: typedef' --- \ No newline at end of file diff --git a/docs/rules/use-isnan/index.html b/docs/rules/use-isnan/index.html index 2c3dbc2a07e..848ec439738 100644 --- a/docs/rules/use-isnan/index.html +++ b/docs/rules/use-isnan/index.html @@ -10,7 +10,8 @@ optionExamples: - 'true' type: functionality -optionsJSON: 'null' +typescriptOnly: false layout: rule title: 'Rule: use-isnan' +optionsJSON: 'null' --- \ No newline at end of file diff --git a/docs/rules/variable-name/index.html b/docs/rules/variable-name/index.html index f7220338766..0808782623f 100644 --- a/docs/rules/variable-name/index.html +++ b/docs/rules/variable-name/index.html @@ -26,6 +26,9 @@ optionExamples: - '[true, "ban-keywords", "check-format", "allow-leading-underscore"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: variable-name' optionsJSON: |- { "type": "array", @@ -42,6 +45,4 @@ "minLength": 0, "maxLength": 5 } -layout: rule -title: 'Rule: variable-name' --- \ No newline at end of file diff --git a/docs/rules/whitespace/index.html b/docs/rules/whitespace/index.html index 4e591aa49a2..259fd6a8e02 100644 --- a/docs/rules/whitespace/index.html +++ b/docs/rules/whitespace/index.html @@ -30,6 +30,9 @@ optionExamples: - '[true, "check-branch", "check-operator", "check-typecast"]' type: style +typescriptOnly: false +layout: rule +title: 'Rule: whitespace' optionsJSON: |- { "type": "array", @@ -48,6 +51,4 @@ "minLength": 0, "maxLength": 7 } -layout: rule -title: 'Rule: whitespace' --- \ No newline at end of file diff --git a/docs/usage/cli/index.md b/docs/usage/cli/index.md index 52c6cf62f37..6992d54d605 100644 --- a/docs/usage/cli/index.md +++ b/docs/usage/cli/index.md @@ -30,15 +30,18 @@ Options: ``` -c, --config configuration file +-e, --exclude exclude globs from path expansion +--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 -o, --out output file +--project tsconfig.json file -r, --rules-dir rules directory -s, --formatters-dir formatters directory --e, --exclude exclude globs from path expansion --t, --format output format (prose, json, verbose, pmd, msbuild, checkstyle, vso) [default: "prose"] +-t, --format output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist) [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 ``` @@ -54,7 +57,7 @@ tslint accepts the following command-line options: to the rules. If no option is specified, the config file named tslint.json is used, so long as it exists in the path. The format of the file is { rules: { /* rules list */ } }, - where /* rules list */ is a key: value comma-seperated list of + where /* rules list */ is a key: value comma-separated list of rulename: rule-options pairs. Rule-options can be either a boolean true/false value denoting whether the rule is used or not, or a list [boolean, ...] where the boolean provides the same role @@ -68,6 +71,9 @@ tslint accepts the following command-line options: This option can be supplied multiple times if you need multiple globs to indicate which files to exclude. +--fix: + Fixes linting errors for select rules. This may overwrite linted files. + --force: Return status code 0 even if there are any lint errors. Useful while running as npm script. @@ -109,6 +115,14 @@ tslint accepts the following command-line options: specified directory as the configuration file for the tests. See the full tslint documentation for more details on how this can be used to test custom rules. +--project: + The location of a tsconfig.json file that will be used to determine which + files will be linted. + +--type-check + Enables the type checker when running linting rules. --project must be + specified in order to enable type checking. + -v, --version: The current version of tslint. diff --git a/docs/usage/tslint-json/index.md b/docs/usage/tslint-json/index.md index 4d2bd202f41..0b3e3135ee0 100644 --- a/docs/usage/tslint-json/index.md +++ b/docs/usage/tslint-json/index.md @@ -20,7 +20,8 @@ A path(s) to a directory of [custom rules][2]. This will always be treated as a * `rules?: any`: Pairs of keys and values where each key is a rule name and each value is the configuration for that rule. If a rule takes no options, you can simply set its value to a boolean, either `true` or `false`, to enable or disable it. If a rule takes options, you set its value to an array where the first value is a boolean indicating if the rule is enabled and the next values are options handled by the rule. -Not all possible rules are listed here, be sure to [check out the full list][3]. +Not all possible rules are listed here, be sure to [check out the full list][3]. These rules are applied to `.ts` and `.tsx` files. +* `jsRules?: any`: Same format as `rules`. These rules are applied to `.js` and `.jsx` files. `tslint.json` configuration files may have JavaScript-style `// single-line` and `/* multi-line */` comments in them (even though this is technically invalid JSON). If this confuses your syntax highlighter, you may want to switch it to JavaScript format. @@ -57,6 +58,24 @@ An example `tslint.json` file might look like this: "check-separator", "check-type" ] + }, + "jsRules": { + "indent": [true, "spaces"], + "no-duplicate-variable": true, + "no-eval": true, + "no-trailing-whitespace": true, + "one-line": [true, "check-open-brace", "check-whitespace"], + "quotemark": [true, "double"], + "semicolon": false, + "triple-equals": [true, "allow-null-check"], + "variable-name": [true, "ban-keywords"], + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] } } ``` diff --git a/package.json b/package.json index a480fc936b1..8665ed97a30 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "tslint", - "version": "4.0.0-dev.1", + "version": "4.0.0-dev.2", "description": "An extensible static analysis linter for the TypeScript language", "bin": { "tslint": "./bin/tslint" }, - "main": "./lib/tslint", - "typings": "./lib/tslint", + "main": "./lib/index.js", + "typings": "./lib/index.d.ts", "repository": { "type": "git", "url": "https://github.com/palantir/tslint.git" @@ -26,11 +26,11 @@ "compile:scripts": "tsc -p scripts", "compile:test": "tsc -p test", "lint": "npm-run-all -p lint:core lint:test", - "lint:core": "tslint src/**/*.ts", - "lint:test": "tslint test/**/*.ts -e test/**/*.test.ts", + "lint:core": "tslint \"src/**/*.ts\"", + "lint:test": "tslint \"test/**/*.ts\" -e \"test/**/*.test.ts\"", "test": "npm-run-all test:pre -p test:mocha test:rules", "test:pre": "cd ./test/config && npm install", - "test:mocha": "mocha --reporter spec --colors build/test/**/*Tests.js build/test/assert.js", + "test:mocha": "mocha --reporter spec --colors \"build/test/**/*Tests.js\" build/test/assert.js", "test:rules": "node ./build/test/ruleTestRunner.js", "verify": "npm-run-all clean compile lint test docs" }, @@ -41,7 +41,8 @@ "glob": "^7.1.1", "optimist": "~0.6.0", "resolve": "^1.1.7", - "underscore.string": "^3.3.4" + "underscore.string": "^3.3.4", + "update-notifier": "^1.0.2" }, "devDependencies": { "@types/chai": "^3.4.34", @@ -66,10 +67,7 @@ "typescript": ">=2.0.0-dev" }, "peerDependencies": { - "typescript": ">=1.7.3 || >=1.8.0-dev || >=1.9.0-dev || >=2.0.0-dev || >=2.1.0-dev || >= 2.2.0-dev" + "typescript": ">=2.0.0-dev || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev" }, - "license": "Apache-2.0", - "typescript": { - "definition": "lib/tslint.d.ts" - } + "license": "Apache-2.0" } diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index a289747f09e..a0ebb235e95 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,12 +1,11 @@ { - "version": "2.0.3", + "version": "2.0.10", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "noUnusedParameters": true, "noUnusedLocals": true, - "declaration": false, - "sourceMap": false, + "sourceMap": true, "target": "es5" } } diff --git a/src/configs/latest.ts b/src/configs/latest.ts index 3579a7245e8..a6523eb76c1 100644 --- a/src/configs/latest.ts +++ b/src/configs/latest.ts @@ -16,16 +16,6 @@ */ export const rules = { - "adjacent-overload-signatures": true, - "cyclomatic-complexity": false, - "no-unsafe-finally": true, - "object-literal-key-quotes": [true, "as-needed"], - "object-literal-shorthand": true, - "only-arrow-functions": [true, "allow-declarations"], - "ordered-imports": [true, { - "import-sources-order": "case-insensitive", - "named-imports-order": "lowercase-last", - }], }; // work around "extends" being a keyword diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 37ec0de33df..77acc19835a 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -17,21 +17,26 @@ /* tslint:disable:object-literal-key-quotes */ export const rules = { + "adjacent-overload-signatures": true, "align": [true, "parameters", "statements", ], + "array-type": [true, "array-simple"], + "arrow-parens": true, "class-name": true, "comment-format": [true, "check-space", ], "curly": true, + "cyclomatic-complexity": false, "eofline": true, "forin": true, "indent": [true, "spaces"], "interface-name": [true, "always-prefix"], "jsdoc-format": true, "label-position": true, + "max-classes-per-file": [true, 1], "max-line-length": [true, 120], "member-access": true, "member-ordering": [true, @@ -53,7 +58,6 @@ export const rules = { ], "no-construct": true, "no-debugger": true, - "no-duplicate-variable": true, "no-empty": true, "no-eval": true, "no-internal-module": true, @@ -64,7 +68,7 @@ export const rules = { "no-string-literal": true, "no-switch-case-fall-through": false, "no-trailing-whitespace": true, - "no-unreachable": true, + "no-unsafe-finally": true, "no-unused-expression": true, "no-unused-new": true, // deprecated as of v4.0 @@ -73,6 +77,8 @@ export const rules = { "no-use-before-declare": false, "no-var-keyword": true, "no-var-requires": true, + "object-literal-key-quotes": [true, "consistent-as-needed"], + "object-literal-shorthand": true, "object-literal-sort-keys": true, "one-line": [true, "check-catch", @@ -84,6 +90,12 @@ export const rules = { "one-variable-per-declaration": [true, "ignore-for-loop", ], + "only-arrow-functions": [true, "allow-declarations"], + "ordered-imports": [true, { + "import-sources-order": "case-insensitive", + "named-imports-order": "case-insensitive", + }], + "prefer-for-of": true, "quotemark": [true, "double", "avoid-escape"], "radix": true, "semicolon": [true, "always"], @@ -154,7 +166,6 @@ export const jsRules = { ], "no-construct": true, "no-debugger": true, - "no-duplicate-key": true, "no-duplicate-variable": true, "no-empty": true, "no-eval": true, @@ -163,7 +174,6 @@ export const jsRules = { "no-string-literal": true, "no-switch-case-fall-through": false, "no-trailing-whitespace": true, - "no-unreachable": true, "no-unused-expression": true, "no-unused-new": true, // disable this rule as it is very heavy performance-wise and not that useful diff --git a/src/configuration.ts b/src/configuration.ts index 4c7241bbf74..e295aeada39 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -32,6 +32,20 @@ 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; +} + export const CONFIG_FILENAME = "tslint.json"; /* tslint:disable:object-literal-key-quotes */ export const DEFAULT_CONFIG = { @@ -43,7 +57,6 @@ export const DEFAULT_CONFIG = { "no-eval": true, "no-trailing-whitespace": true, "no-unsafe-finally": true, - "no-var-keyword": true, "one-line": [true, "check-open-brace", "check-whitespace"], "quotemark": [true, "double"], "semicolon": [true, "always"], @@ -61,7 +74,6 @@ export const DEFAULT_CONFIG = { "class-name": true, "comment-format": [true, "check-space"], "indent": [true, "spaces"], - "no-duplicate-variable": true, "no-eval": true, "no-internal-module": true, "no-trailing-whitespace": true, @@ -99,11 +111,19 @@ const BUILT_IN_CONFIG = /^tslint:(.*)$/; * @param configFile A path to a config file, this can be null if the location of a config is not known * @param inputFileLocation A path to the current file being linted. This is the starting location * of the search for a configuration. - * @returns A TSLint configuration object + * @returns Load status for a TSLint configuration object */ -export function findConfiguration(configFile: string, inputFilePath: string): IConfigurationFile { - const configPath = findConfigurationPath(configFile, inputFilePath); - return loadConfigurationFromPath(configPath); +export function findConfiguration(configFile: string, inputFilePath: string): IConfigurationLoadResult { + const path = findConfigurationPath(configFile, inputFilePath); + const loadResult: IConfigurationLoadResult = { path }; + + try { + loadResult.results = loadConfigurationFromPath(path); + } catch (error) { + loadResult.error = error; + } + + return loadResult; } /** @@ -171,14 +191,14 @@ export function loadConfigurationFromPath(configFilePath: string): IConfiguratio const configFileDir = path.dirname(resolvedConfigFilePath); configFile.rulesDirectory = getRulesDirectories(configFile.rulesDirectory, configFileDir); - configFile.extends = arrayify(configFile.extends); + // load configurations, in order, using their identifiers or relative paths + // apply the current configuration last by placing it last in this array + const configs = arrayify(configFile.extends).map((name) => { + const nextConfigFilePath = resolveConfigurationPath(name, configFileDir); + return loadConfigurationFromPath(nextConfigFilePath); + }).concat([configFile]); - for (const name of configFile.extends) { - const baseConfigFilePath = resolveConfigurationPath(name, configFileDir); - const baseConfigFile = loadConfigurationFromPath(baseConfigFilePath); - configFile = extendConfigurationFile(configFile, baseConfigFile); - } - return configFile; + return configs.reduce(extendConfigurationFile, {}); } } @@ -212,28 +232,29 @@ function resolveConfigurationPath(filePath: string, relativeTo?: string) { } } -export function extendConfigurationFile(config: IConfigurationFile, baseConfig: IConfigurationFile): IConfigurationFile { +export function extendConfigurationFile(targetConfig: IConfigurationFile, + nextConfigSource: IConfigurationFile): IConfigurationFile { let combinedConfig: IConfigurationFile = {}; - const baseRulesDirectory = arrayify(baseConfig.rulesDirectory); - const configRulesDirectory = arrayify(config.rulesDirectory); - combinedConfig.rulesDirectory = configRulesDirectory.concat(baseRulesDirectory); + const configRulesDirectory = arrayify(targetConfig.rulesDirectory); + const nextConfigRulesDirectory = arrayify(nextConfigSource.rulesDirectory); + combinedConfig.rulesDirectory = configRulesDirectory.concat(nextConfigRulesDirectory); - combinedConfig.rules = {}; - for (const name of Object.keys(objectify(baseConfig.rules))) { - combinedConfig.rules[name] = baseConfig.rules[name]; - } - for (const name of Object.keys(objectify(config.rules))) { - combinedConfig.rules[name] = config.rules[name]; - } + const combineProperties = (targetProperty: any, nextProperty: any) => { + let combinedProperty: any = {}; + for (const name of Object.keys(objectify(targetProperty))) { + combinedProperty[name] = targetProperty[name]; + } + // next config source overwrites the target config object + for (const name of Object.keys(objectify(nextProperty))) { + combinedProperty[name] = nextProperty[name]; + } + return combinedProperty; + }; - combinedConfig.jsRules = {}; - for (const name of Object.keys(objectify(baseConfig.jsRules))) { - combinedConfig.jsRules[name] = baseConfig.jsRules[name]; - } - for (const name of Object.keys(objectify(config.jsRules))) { - combinedConfig.jsRules[name] = config.jsRules[name]; - } + combinedConfig.rules = combineProperties(targetConfig.rules, nextConfigSource.rules); + combinedConfig.jsRules = combineProperties(targetConfig.jsRules, nextConfigSource.jsRules); + combinedConfig.linterOptions = combineProperties(targetConfig.linterOptions, nextConfigSource.linterOptions); return combinedConfig; } diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index b67ca86b43a..43f14d14017 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -48,7 +48,7 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { private getStartOfLinePosition(node: ts.SourceFile, position: number, lineOffset = 0) { return node.getPositionOfLineAndCharacter( - node.getLineAndCharacterOfPosition(position).line + lineOffset, 0 + node.getLineAndCharacterOfPosition(position).line + lineOffset, 0, ); } diff --git a/src/formatters/applyFixesFormatter.ts b/src/formatters/applyFixesFormatter.ts deleted file mode 100644 index e012ef40ad4..00000000000 --- a/src/formatters/applyFixesFormatter.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {AbstractFormatter} from "../language/formatter/abstractFormatter"; -import {IFormatterMetadata} from "../language/formatter/formatter"; -import {Fix, RuleFailure} from "../language/rule/rule"; -import * as Utils from "../utils"; -import * as fs from "fs"; - -export class Formatter extends AbstractFormatter { - /* tslint:disable:object-literal-sort-keys */ - public static metadata: IFormatterMetadata = { - formatterName: "applyFixes", - description: "Automatically fixes lint failures.", - descriptionDetails: Utils.dedent` - Modifies source files and applies fixes for lint failures where possible. Changes - should be tested as not all fixes preserve semantics.`, - sample: Utils.dedent` - All done. Remember to test the changes, as not all fixes preserve semantics.`, - consumer: "machine", - }; - /* tslint:enable:object-literal-sort-keys */ - - public format(failures: RuleFailure[]): string { - const files: {[file: string]: boolean} = {}; - failures.map(f => files[f.getFileName()] = true); - const log: string[] = []; - for (const file of Object.keys(files)) { - log.push(`Applying fixes to ${file}`); - let content = fs.readFileSync(file, {encoding: "utf-8"}); - const fixes = failures.filter(f => f.getFileName() === file).map(f => f.getFix()).filter(f => !!f); - content = Fix.applyAll(content, fixes); - fs.writeFileSync(file, content, {encoding: "utf-8"}); - } - log.push("All done. Remember to test the changes, as not all fixes preserve semantics."); - return log.join("\n") + "\n"; - } -} diff --git a/src/formatters/proseFormatter.ts b/src/formatters/proseFormatter.ts index 1331c9248ab..b70024a4fa0 100644 --- a/src/formatters/proseFormatter.ts +++ b/src/formatters/proseFormatter.ts @@ -29,12 +29,30 @@ export class Formatter extends AbstractFormatter { }; /* tslint:enable:object-literal-sort-keys */ - public format(failures: RuleFailure[]): string { - if (failures.length === 0) { + public format(failures: RuleFailure[], fixes?: RuleFailure[]): string { + if (failures.length === 0 && (!fixes || fixes.length === 0)) { return ""; } - const outputLines = failures.map((failure: RuleFailure) => { + let fixLines: string[] = []; + if (fixes) { + let perFileFixes: { [fileName: string]: number } = {}; + for (const fix of fixes) { + if (perFileFixes[fix.getFileName()] == null) { + perFileFixes[fix.getFileName()] = 1; + } else { + perFileFixes[fix.getFileName()]++; + } + } + + Object.keys(perFileFixes).forEach((fixedFile: string) => { + const fixCount = perFileFixes[fixedFile]; + fixLines.push(`Fixed ${fixCount} error(s) in ${fixedFile}`); + }); + fixLines.push(""); // add a blank line between fixes and failures + } + + let errorLines = failures.map((failure: RuleFailure) => { const fileName = failure.getFileName(); const failureString = failure.getFailure(); @@ -44,6 +62,6 @@ export class Formatter extends AbstractFormatter { return `${fileName}${positionTuple}: ${failureString}`; }); - return outputLines.join("\n") + "\n"; + return fixLines.concat(errorLines).join("\n") + "\n"; } } diff --git a/src/formatters/stylishFormatter.ts b/src/formatters/stylishFormatter.ts index bd65d14e2f1..f9ea5f75ffe 100644 --- a/src/formatters/stylishFormatter.ts +++ b/src/formatters/stylishFormatter.ts @@ -59,12 +59,13 @@ export class Formatter extends AbstractFormatter { currentFile = fileName; } - const failureString = failure.getFailure(); + let failureString = failure.getFailure(); + failureString = colors.yellow(failureString); // Rule let ruleName = failure.getRuleName(); ruleName = this.pad(ruleName, ruleMaxSize); - ruleName = colors.yellow(ruleName); + ruleName = colors.grey(ruleName); // Lines const lineAndCharacter = failure.getStartPosition().getLineAndCharacter(); diff --git a/src/lint.ts b/src/index.ts similarity index 61% rename from src/lint.ts rename to src/index.ts index 35f3a1af219..faf6ede9279 100644 --- a/src/lint.ts +++ b/src/index.ts @@ -15,13 +15,15 @@ * limitations under the License. */ -import * as configuration from "./configuration"; -import * as formatters from "./formatters"; +import * as Configuration from "./configuration"; +import * as Formatters from "./formatters"; import {RuleFailure} from "./language/rule/rule"; -import * as rules from "./rules"; -import * as test from "./test"; -import * as linter from "./tslint"; -import * as utils from "./utils"; +import * as Linter from "./linter"; +import * as Rules from "./rules"; +import * as Test from "./test"; +import * as Utils from "./utils"; + +export { Configuration, Formatters, Linter, Rules, Test, Utils }; export * from "./language/rule/rule"; export * from "./enableDisableRules"; @@ -32,34 +34,16 @@ export * from "./language/languageServiceHost"; export * from "./language/walker"; export * from "./language/formatter/formatter"; -export var Configuration = configuration; -export var Formatters = formatters; -export var Linter = linter; -export var Rules = rules; -export var Test = test; -export var Utils = utils; - export interface LintResult { failureCount: number; failures: RuleFailure[]; + fixes?: RuleFailure[]; format: string | Function; output: string; } -export interface ILinterOptionsRaw { - configuration?: any; - formatter?: string | Function; - formattersDirectory?: string; - rulesDirectory?: string | string[]; -} - -export interface ILinterOptions extends ILinterOptionsRaw { - configuration: any; - formatter: string | Function; - rulesDirectory: string | string[]; -} - -export interface IMultiLinterOptions { +export interface ILinterOptions { + fix: boolean; formatter?: string | Function; formattersDirectory?: string; rulesDirectory?: string | string[]; diff --git a/src/language/formatter/formatter.ts b/src/language/formatter/formatter.ts index 50b4de7c9e0..7801a9a510b 100644 --- a/src/language/formatter/formatter.ts +++ b/src/language/formatter/formatter.ts @@ -47,5 +47,10 @@ export interface IFormatterMetadata { export type ConsumerType = "human" | "machine"; export interface IFormatter { - format(failures: RuleFailure[]): string; + /** + * Formats linter results + * @param {RuleFailure[]} failures Linter errors that were not fixed + * @param {RuleFailure[]} fixes Fixed linter errors. Available when the `--fix` argument is used on the command line + */ + format(failures: RuleFailure[], fixes?: RuleFailure[]): string; } diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index c05c1f25fb4..1859227dd78 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -17,9 +17,8 @@ import * as ts from "typescript"; -import {IOptions} from "../../lint"; import {RuleWalker} from "../walker/ruleWalker"; -import {IDisabledInterval, IRule, IRuleMetadata, RuleFailure} from "./rule"; +import {IDisabledInterval, IOptions, IRule, IRuleMetadata, RuleFailure} from "./rule"; export abstract class AbstractRule implements IRule { public static metadata: IRuleMetadata; diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 815cfefaef0..3cdd626cc27 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -48,7 +48,7 @@ export interface IRuleMetadata { /** * An explanation of the available options for the rule. */ - optionsDescription?: string; + optionsDescription: string; /** * Schema of the options the rule accepts. @@ -74,9 +74,9 @@ export interface IRuleMetadata { requiresTypeInfo?: boolean; /** - * Whether or not the rule use for TypeScript only. + * Whether or not the rule use for TypeScript only. If `false`, this rule may be used with .js files. */ - typescriptOnly?: boolean; + typescriptOnly: boolean; } export type RuleType = "functionality" | "maintainability" | "style" | "typescript"; diff --git a/src/language/rule/typedRule.ts b/src/language/rule/typedRule.ts index 1489165bd55..661eda826c0 100644 --- a/src/language/rule/typedRule.ts +++ b/src/language/rule/typedRule.ts @@ -18,9 +18,14 @@ import * as ts from "typescript"; import {AbstractRule} from "./abstractRule"; -import {RuleFailure} from "./rule"; +import {IRule, RuleFailure} from "./rule"; export abstract class TypedRule extends AbstractRule { + + public static isTypedRule(rule: IRule): rule is TypedRule { + return "applyWithProgram" in rule; + } + public apply(_sourceFile: ts.SourceFile): RuleFailure[] { // if no program is given to the linter, throw an error throw new Error(`${this.getOptions().ruleName} requires type checking`); diff --git a/src/language/walker/programAwareRuleWalker.ts b/src/language/walker/programAwareRuleWalker.ts index 6b29e1fdcd4..9fc9b678ca1 100644 --- a/src/language/walker/programAwareRuleWalker.ts +++ b/src/language/walker/programAwareRuleWalker.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import {IOptions} from "../../lint"; +import {IOptions} from "../rule/rule"; import {RuleWalker} from "./ruleWalker"; export class ProgramAwareRuleWalker extends RuleWalker { diff --git a/src/language/walker/ruleWalker.ts b/src/language/walker/ruleWalker.ts index bd9a9162eb5..7c17dee7602 100644 --- a/src/language/walker/ruleWalker.ts +++ b/src/language/walker/ruleWalker.ts @@ -17,8 +17,7 @@ import * as ts from "typescript"; -import {IOptions} from "../../lint"; -import {Fix, IDisabledInterval, Replacement, RuleFailure} from "../rule/rule"; +import {Fix, IDisabledInterval, IOptions, Replacement, RuleFailure} from "../rule/rule"; import {doesIntersect} from "../utils"; import {SyntaxWalker} from "./syntaxWalker"; diff --git a/src/language/walker/skippableTokenAwareRuleWalker.ts b/src/language/walker/skippableTokenAwareRuleWalker.ts index a374fad746d..e3a6e6857ce 100644 --- a/src/language/walker/skippableTokenAwareRuleWalker.ts +++ b/src/language/walker/skippableTokenAwareRuleWalker.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import {IOptions} from "../../lint"; +import {IOptions} from "../rule/rule"; import {RuleWalker} from "./ruleWalker"; export class SkippableTokenAwareRuleWalker extends RuleWalker { diff --git a/src/tslintMulti.ts b/src/linter.ts similarity index 53% rename from src/tslintMulti.ts rename to src/linter.ts index 0cdbf78c6a8..e4fe8d867f1 100644 --- a/src/tslintMulti.ts +++ b/src/linter.ts @@ -15,33 +15,33 @@ * limitations under the License. */ -import { existsSync } from "fs"; +import * as fs from "fs"; import * as ts from "typescript"; import { DEFAULT_CONFIG, + IConfigurationFile, findConfiguration, findConfigurationPath, getRelativePath, getRulesDirectories, - IConfigurationFile, loadConfigurationFromPath, } from "./configuration"; import { EnableDisableRulesWalker } from "./enableDisableRules"; import { findFormatter } from "./formatterLoader"; +import { ILinterOptions, LintResult } from "./index"; import { IFormatter } from "./language/formatter/formatter"; -import { RuleFailure } from "./language/rule/rule"; +import { Fix, IRule, RuleFailure } from "./language/rule/rule"; import { TypedRule } from "./language/rule/typedRule"; -import { getSourceFile } from "./language/utils"; -import { IMultiLinterOptions, IRule, LintResult } from "./lint"; +import * as utils from "./language/utils"; import { loadRules } from "./ruleLoader"; -import { arrayify } from "./utils"; +import { arrayify, dedent } from "./utils"; /** * Linter that can lint multiple files in consecutive runs. */ -class MultiLinter { - public static VERSION = "4.0.0-dev.1"; +class Linter { + public static VERSION = "4.0.0-dev.2"; public static findConfiguration = findConfiguration; public static findConfigurationPath = findConfigurationPath; @@ -49,6 +49,7 @@ class MultiLinter { public static loadConfigurationFromPath = loadConfigurationFromPath; private failures: RuleFailure[] = []; + private fixes: RuleFailure[] = []; /** * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. @@ -65,7 +66,7 @@ class MultiLinter { const { config } = ts.readConfigFile(configFile, ts.sys.readFile); const parseConfigHost = { - fileExists: existsSync, + fileExists: fs.existsSync, readDirectory: ts.sys.readDirectory, useCaseSensitiveFileNames: true, }; @@ -81,64 +82,55 @@ class MultiLinter { * files and excludes declaration (".d.ts") files. */ public static getFileNames(program: ts.Program): string[] { - return program.getSourceFiles().map(s => s.fileName).filter(l => l.substr(-5) !== ".d.ts"); + return program.getSourceFiles().map((s) => s.fileName).filter((l) => l.substr(-5) !== ".d.ts"); } - constructor(private options: IMultiLinterOptions, private program?: ts.Program) { - // Empty + constructor(private options: ILinterOptions, private program?: ts.Program) { + if (typeof options !== "object") { + throw new Error("Unknown Linter options type: " + typeof options); + } + if (( options).configuration != null) { + throw new Error("ILinterOptions does not contain the property `configuration` as of version 4. " + + "Did you mean to pass the `IConfigurationFile` object to lint() ? "); + } } public lint(fileName: string, source?: string, configuration: IConfigurationFile = DEFAULT_CONFIG): void { - let sourceFile: ts.SourceFile; - if (this.program) { - sourceFile = this.program.getSourceFile(fileName); - // check if the program has been type checked - if (sourceFile && !("resolvedModules" in sourceFile)) { - throw new Error("Program must be type checked before linting"); + const enabledRules = this.getEnabledRules(fileName, source, configuration); + let sourceFile = this.getSourceFile(fileName, source); + let hasLinterRun = false; + 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); + source = fs.readFileSync(fileName, { encoding: "utf-8" }); + if (fixes.length > 0) { + this.fixes = this.fixes.concat(ruleFailures); + source = Fix.applyAll(source, fixes); + fs.writeFileSync(fileName, source, { encoding: "utf-8" }); + + // reload AST if file is modified + sourceFile = this.getSourceFile(fileName, source); + } + fileFailures = fileFailures.concat(ruleFailures); } - } else { - sourceFile = getSourceFile(fileName, source); - } - - if (sourceFile === undefined) { - throw new Error(`Invalid source file: ${fileName}. Ensure that the files supplied to lint have a .ts or .tsx extension.`); + hasLinterRun = true; } - // walk the code first to find all the intervals where rules are disabled - const rulesWalker = new EnableDisableRulesWalker(sourceFile, { - disabledIntervals: [], - ruleName: "", - }); - rulesWalker.walk(sourceFile); - const enableDisableRuleMap = rulesWalker.enableDisableRuleMap; - - const rulesDirectories = arrayify(this.options.rulesDirectory) - .concat(arrayify(configuration.rulesDirectory)); - const configurationRules = configuration.rules; - const jsConfiguration = configuration.jsRules; - const isJs = fileName.substr(-3) === ".js"; - let configuredRules: IRule[]; - - if (isJs) { - configuredRules = loadRules(jsConfiguration, enableDisableRuleMap, rulesDirectories, true); - } else { - configuredRules = loadRules(configurationRules, enableDisableRuleMap, rulesDirectories, false); - } - - const enabledRules = configuredRules.filter((r) => r.isEnabled()); - for (let rule of enabledRules) { - let ruleFailures: RuleFailure[] = []; - if (this.program && rule instanceof TypedRule) { - ruleFailures = rule.applyWithProgram(sourceFile, this.program); - } else { - ruleFailures = rule.apply(sourceFile); - } - for (let ruleFailure of ruleFailures) { - if (!this.containsRule(this.failures, ruleFailure)) { - this.failures.push(ruleFailure); + // make a 1st pass or make a 2nd pass if there were any fixes because the positions may be off + if (!hasLinterRun || this.fixes.length > 0) { + fileFailures = []; + for (let rule of enabledRules) { + const ruleFailures = this.applyRule(rule, sourceFile); + if (ruleFailures.length > 0) { + fileFailures = fileFailures.concat(ruleFailures); } } } + this.failures = this.failures.concat(fileFailures); } public getResult(): LintResult { @@ -153,22 +145,80 @@ class MultiLinter { throw new Error(`formatter '${formatterName}' not found`); } - const output = formatter.format(this.failures); + const output = formatter.format(this.failures, this.fixes); return { failureCount: this.failures.length, failures: this.failures, + fixes: this.fixes, format: formatterName, output, }; } + private applyRule(rule: IRule, sourceFile: ts.SourceFile) { + let ruleFailures: RuleFailure[] = []; + if (this.program && TypedRule.isTypedRule(rule)) { + ruleFailures = rule.applyWithProgram(sourceFile, this.program); + } else { + ruleFailures = rule.apply(sourceFile); + } + let fileFailures: RuleFailure[] = []; + for (let ruleFailure of ruleFailures) { + if (!this.containsRule(this.failures, ruleFailure)) { + fileFailures.push(ruleFailure); + } + } + return fileFailures; + } + + private getEnabledRules(fileName: string, source?: string, configuration: IConfigurationFile = DEFAULT_CONFIG): IRule[] { + const sourceFile = this.getSourceFile(fileName, source); + + // walk the code first to find all the intervals where rules are disabled + const rulesWalker = new EnableDisableRulesWalker(sourceFile, { + disabledIntervals: [], + ruleName: "", + }); + 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()); + } + + private getSourceFile(fileName: string, source?: string) { + let sourceFile: ts.SourceFile; + if (this.program) { + sourceFile = this.program.getSourceFile(fileName); + // check if the program has been type checked + if (sourceFile && !("resolvedModules" in sourceFile)) { + throw new Error("Program must be type checked before linting"); + } + } else { + sourceFile = utils.getSourceFile(fileName, source); + } + + if (sourceFile === undefined) { + const INVALID_SOURCE_ERROR = dedent` + Invalid source file: ${fileName}. Ensure that the files supplied to lint have a .ts, .tsx, .js or .jsx extension. + `; + throw new Error(INVALID_SOURCE_ERROR); + } + return sourceFile; + } + private containsRule(rules: RuleFailure[], rule: RuleFailure) { return rules.some((r) => r.equals(rule)); } } // tslint:disable-next-line:no-namespace -namespace MultiLinter {} +namespace Linter { } -export = MultiLinter; +export = Linter; diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index 9e779072704..5a4e79860c7 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -46,7 +46,7 @@ export function loadRules(ruleConfiguration: {[name: string]: any}, if (Rule == null) { notFoundRules.push(ruleName); } else { - if (isJs && Rule.metadata.typescriptOnly != null && Rule.metadata.typescriptOnly) { + 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 @@ -75,8 +75,10 @@ export function loadRules(ruleConfiguration: {[name: string]: any}, throw new Error(ERROR_MESSAGE); } else if (notAllowedInJsRules.length > 0) { const JS_ERROR_MESSAGE = ` - Could not apply to JavaScript files for the following rules specified in the configuration: - ${notAllowedInJsRules.join("\n")} + Following rules specified in configuration couldn't be applied to .js or .jsx files: + ${notAllowedInJsRules.join("\n")} + + Make sure to exclude them from "jsRules" section of your tslint.json. `; throw new Error(JS_ERROR_MESSAGE); diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index bbe11db548e..ff0de18c935 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { @@ -28,12 +28,15 @@ export class Rule extends Lint.Rules.AbstractRule { optionsDescription: "Not configurable.", options: null, optionExamples: ["true"], + rationale: "Improves readability and organization by grouping naturally related items together.", type: "typescript", typescriptOnly: true, }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (name: string) => `All '${name}' signatures should be adjacent`; + public static FAILURE_STRING_FACTORY = (name: string) => { + return `All '${name}' signatures should be adjacent`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new AdjacentOverloadSignaturesWalker(sourceFile, this.getOptions())); @@ -41,34 +44,63 @@ export class Rule extends Lint.Rules.AbstractRule { } class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { + public visitSourceFile(node: ts.SourceFile) { + this.visitStatements(node.statements); + super.visitSourceFile(node); + } + + public visitModuleDeclaration(node: ts.ModuleDeclaration) { + const { body } = node; + if (body && body.kind === ts.SyntaxKind.ModuleBlock) { + this.visitStatements((body as ts.ModuleBlock).statements); + } + super.visitModuleDeclaration(node); + } public visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void { - this.checkNode(node); + this.checkOverloadsAdjacent(node.members, member => member.name && getTextOfPropertyName(member.name)); super.visitInterfaceDeclaration(node); } - public visitTypeLiteral(node: ts.TypeLiteralNode): void { - this.checkNode(node); + public visitClassDeclaration(node: ts.ClassDeclaration) { + this.visitMembers(node.members); + super.visitClassDeclaration(node); + } + + public visitTypeLiteral(node: ts.TypeLiteralNode) { + this.visitMembers(node.members); super.visitTypeLiteral(node); } - public checkNode(node: ts.TypeLiteralNode | ts.InterfaceDeclaration) { - let last: string = undefined; - const seen: { [name: string]: boolean } = {}; - for (const member of node.members) { - if (member.name !== undefined) { - const methodName = getTextOfPropertyName(member.name); - if (methodName !== undefined) { - if (seen.hasOwnProperty(methodName) && last !== methodName) { - this.addFailure(this.createFailure(member.getStart(), member.getWidth(), - Rule.FAILURE_STRING_FACTORY(methodName))); - } - last = methodName; - seen[methodName] = true; - } + private visitStatements(statements: ts.Statement[]) { + this.checkOverloadsAdjacent(statements, statement => { + if (statement.kind === ts.SyntaxKind.FunctionDeclaration) { + const name = (statement as ts.FunctionDeclaration).name; + return name && name.text; } else { - last = undefined; + return undefined; + } + }); + } + + private visitMembers(members: (ts.TypeElement | ts.ClassElement)[]) { + this.checkOverloadsAdjacent(members, member => member.name && getTextOfPropertyName(member.name)); + } + + /** 'getOverloadName' may return undefined for nodes that cannot be overloads, e.g. a `const` declaration. */ + private checkOverloadsAdjacent(overloads: T[], getOverloadName: (node: T) => string | undefined) { + let last: string | undefined = undefined; + const seen: { [name: string]: true } = Object.create(null); + for (const node of overloads) { + 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))); + } + seen[name] = true; } + last = name; } } } diff --git a/src/rules/alignRule.ts b/src/rules/alignRule.ts index 3740d5d34d8..44bb0aa7256 100644 --- a/src/rules/alignRule.ts +++ b/src/rules/alignRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/arrayTypeRule.ts b/src/rules/arrayTypeRule.ts index 96918f19d7d..7b294c9e1d2 100644 --- a/src/rules/arrayTypeRule.ts +++ b/src/rules/arrayTypeRule.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_ARRAY = "array"; const OPTION_GENERIC = "generic"; diff --git a/src/rules/arrowParensRule.ts b/src/rules/arrowParensRule.ts index 39a7bdd8485..ea877d5d9a1 100644 --- a/src/rules/arrowParensRule.ts +++ b/src/rules/arrowParensRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -58,8 +58,10 @@ class ArrowParensWalker extends Lint.RuleWalker { } if ((firstToken.kind !== ts.SyntaxKind.OpenParenToken || lastToken.kind !== ts.SyntaxKind.CloseParenToken) - && !isGenerics && node.flags !== ts.NodeFlags.Async) { - this.addFailure(this.createFailure(position, width, Rule.FAILURE_STRING)); + && !isGenerics && node.flags !== ts.NodeFlags.Async) { + + 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)); } } super.visitArrowFunction(node); diff --git a/src/rules/banRule.ts b/src/rules/banRule.ts index bd4b4697119..341f7276026 100644 --- a/src/rules/banRule.ts +++ b/src/rules/banRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -45,7 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_FACTORY = (expression: string, messageAddition?: string) => { return `Calls to '${expression}' are not allowed.${messageAddition ? " " + messageAddition : ""}`; - }; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const options = this.getOptions(); diff --git a/src/rules/classNameRule.ts b/src/rules/classNameRule.ts index cb5c050ac33..802fdbf41a0 100644 --- a/src/rules/classNameRule.ts +++ b/src/rules/classNameRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index 31d02991b96..5b074c364e9 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_SPACE = "check-space"; const OPTION_LOWERCASE = "check-lowercase"; diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts new file mode 100644 index 00000000000..b5d2b265ebd --- /dev/null +++ b/src/rules/completedDocsRule.ts @@ -0,0 +1,121 @@ +/** + * @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.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "completed-docs", + description: "Enforces documentation for important items be filled out.", + optionsDescription: Lint.Utils.dedent` + Either \`true\` to enable for all, or any of + \`["classes", "functions", "methods", "properties"] + to choose individual ones.\``, + options: { + type: "array", + items: { + type: "string", + enum: ["classes", "functions", "methods", "properties"], + }, + }, + optionExamples: ["true", `[true, ["classes", "functions"]`], + type: "style", + typescriptOnly: false, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING_EXIST = " must have documentation."; + + public static ARGUMENT_CLASSES = "classes"; + public static ARGUMENT_FUNCTIONS = "functions"; + public static ARGUMENT_METHODS = "methods"; + public static ARGUMENT_PROPERTIES = "properties"; + + public static defaultArguments = [ + Rule.ARGUMENT_CLASSES, + Rule.ARGUMENT_FUNCTIONS, + Rule.ARGUMENT_METHODS, + Rule.ARGUMENT_PROPERTIES, + ]; + + public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + const options = this.getOptions(); + const completedDocsWalker = new CompletedDocsWalker(sourceFile, options, program); + + const nodesToCheck = this.getNodesToCheck(options.ruleArguments); + completedDocsWalker.setNodesToCheck(nodesToCheck); + + return this.applyWithWalker(completedDocsWalker); + } + + private getNodesToCheck(ruleArguments: string[]) { + return ruleArguments.length === 0 ? Rule.defaultArguments : ruleArguments; + } +} + +export class CompletedDocsWalker extends Lint.ProgramAwareRuleWalker { + private nodesToCheck: { [i: string]: boolean } = {}; + + public setNodesToCheck(nodesToCheck: string[]): void { + for (const nodeType of nodesToCheck) { + this.nodesToCheck[nodeType] = true; + } + } + + public visitClassDeclaration(node: ts.ClassDeclaration): void { + this.checkComments(node, Rule.ARGUMENT_CLASSES); + super.visitClassDeclaration(node); + } + + public visitFunctionDeclaration(node: ts.FunctionDeclaration): void { + this.checkComments(node, Rule.ARGUMENT_FUNCTIONS); + super.visitFunctionDeclaration(node); + } + + public visitPropertyDeclaration(node: ts.PropertyDeclaration): void { + this.checkComments(node, Rule.ARGUMENT_PROPERTIES); + super.visitPropertyDeclaration(node); + } + + public visitMethodDeclaration(node: ts.MethodDeclaration): void { + this.checkComments(node, Rule.ARGUMENT_METHODS); + super.visitMethodDeclaration(node); + } + + private checkComments(node: ts.Declaration, nodeToCheck: string): void { + if (!this.nodesToCheck[nodeToCheck]) { + return; + } + + const comments = this.getTypeChecker().getSymbolAtLocation(node.name).getDocumentationComment(); + + if (comments.map(comment => comment.text).join("").trim() === "") { + this.addFailure(this.createDocumentationFailure(node, nodeToCheck)); + } + } + + private createDocumentationFailure(node: ts.Declaration, nodeToCheck: string): Lint.RuleFailure { + 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); + } +} diff --git a/src/rules/curlyRule.ts b/src/rules/curlyRule.ts index 0d1c949f3b4..bcddb77b349 100644 --- a/src/rules/curlyRule.ts +++ b/src/rules/curlyRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/cyclomaticComplexityRule.ts b/src/rules/cyclomaticComplexityRule.ts index 9e1f89bad7d..4459603f4eb 100644 --- a/src/rules/cyclomaticComplexityRule.ts +++ b/src/rules/cyclomaticComplexityRule.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -50,13 +50,17 @@ export class Rule extends Lint.Rules.AbstractRule { }, optionExamples: ["true", "[true, 20]"], type: "maintainability", + typescriptOnly: false, }; /* tslint:enable:object-literal-sort-keys */ - public static ANONYMOUS_FAILURE_STRING = (expected: number, actual: number) => - `The function has a cyclomatic complexity of ${actual} which is higher than the threshold of ${expected}`; - public static NAMED_FAILURE_STRING = (expected: number, actual: number, name: string) => - `The function ${name} has a cyclomatic complexity of ${actual} which is higher than the threshold of ${expected}`; + public static ANONYMOUS_FAILURE_STRING = (expected: number, actual: number) => { + return `The function has a cyclomatic complexity of ${actual} which is higher than the threshold of ${expected}`; + } + + public static NAMED_FAILURE_STRING = (expected: number, actual: number, name: string) => { + return `The function ${name} has a cyclomatic complexity of ${actual} which is higher than the threshold of ${expected}`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new CyclomaticComplexityWalker(sourceFile, this.getOptions(), this.threshold)); diff --git a/src/rules/eoflineRule.ts b/src/rules/eoflineRule.ts index 3380ebe849f..65753975df1 100644 --- a/src/rules/eoflineRule.ts +++ b/src/rules/eoflineRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/fileHeaderRule.ts b/src/rules/fileHeaderRule.ts index c7da278ea3d..1434482e00c 100644 --- a/src/rules/fileHeaderRule.ts +++ b/src/rules/fileHeaderRule.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/forinRule.ts b/src/rules/forinRule.ts index 92c10e53b67..2459cabf43d 100644 --- a/src/rules/forinRule.ts +++ b/src/rules/forinRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/indentRule.ts b/src/rules/indentRule.ts index 7d56502b81f..5cdb939da04 100644 --- a/src/rules/indentRule.ts +++ b/src/rules/indentRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_USE_TABS = "tabs"; const OPTION_USE_SPACES = "spaces"; @@ -77,10 +77,11 @@ class IndentWalker extends Lint.RuleWalker { } let endOfComment = -1; + let endOfTemplateString = -1; const scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text); for (let lineStart of node.getLineStarts()) { - if (lineStart < endOfComment) { - // skip checking lines inside multi-line comments + if (lineStart < endOfComment || lineStart < endOfTemplateString) { + // skip checking lines inside multi-line comments or template strings continue; } @@ -104,6 +105,27 @@ class IndentWalker extends Lint.RuleWalker { const commentRanges = ts.getTrailingCommentRanges(node.text, lineStart); if (commentRanges) { endOfComment = commentRanges[commentRanges.length - 1].end; + } else { + let scanType = currentScannedType; + + // scan until we reach end of line, skipping over template strings + while (scanType !== ts.SyntaxKind.NewLineTrivia && scanType !== ts.SyntaxKind.EndOfFileToken) { + if (scanType === ts.SyntaxKind.NoSubstitutionTemplateLiteral) { + // template string without expressions - skip past it + endOfTemplateString = scanner.getStartPos() + scanner.getTokenText().length; + } else if (scanType === ts.SyntaxKind.TemplateHead) { + // find end of template string containing expressions... + while (scanType !== ts.SyntaxKind.TemplateTail && scanType !== ts.SyntaxKind.EndOfFileToken) { + scanType = scanner.scan(); + if (scanType === ts.SyntaxKind.CloseBraceToken) { + scanType = scanner.reScanTemplateToken(); + } + } + // ... and skip past it + endOfTemplateString = scanner.getStartPos() + scanner.getTokenText().length; + } + scanType = scanner.scan(); + } } if (currentScannedType === ts.SyntaxKind.SingleLineCommentTrivia diff --git a/src/rules/interfaceNameRule.ts b/src/rules/interfaceNameRule.ts index 431967764e1..5cea37f4c65 100644 --- a/src/rules/interfaceNameRule.ts +++ b/src/rules/interfaceNameRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_ALWAYS = "always-prefix"; const OPTION_NEVER = "never-prefix"; diff --git a/src/rules/jsdocFormatRule.ts b/src/rules/jsdocFormatRule.ts index f27e38afaab..40e8f172804 100644 --- a/src/rules/jsdocFormatRule.ts +++ b/src/rules/jsdocFormatRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/labelPositionRule.ts b/src/rules/labelPositionRule.ts index 1301692f064..30650f1af87 100644 --- a/src/rules/labelPositionRule.ts +++ b/src/rules/labelPositionRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/linebreakStyleRule.ts b/src/rules/linebreakStyleRule.ts index 3fade3fb6e7..a8cff31c5f7 100644 --- a/src/rules/linebreakStyleRule.ts +++ b/src/rules/linebreakStyleRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_LINEBREAK_STYLE_CRLF = "CRLF"; const OPTION_LINEBREAK_STYLE_LF = "LF"; diff --git a/src/rules/maxClassesPerFileRule.ts b/src/rules/maxClassesPerFileRule.ts index 0076fbc9311..cd7d112b10b 100644 --- a/src/rules/maxClassesPerFileRule.ts +++ b/src/rules/maxClassesPerFileRule.ts @@ -1,4 +1,4 @@ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -27,6 +27,7 @@ export class Rule extends Lint.Rules.AbstractRule { }, optionExamples: ["[true, 1]", "[true, 5]"], type: "maintainability", + typescriptOnly: false, }; /* tslint:enable:object-literal-sort-keys */ diff --git a/src/rules/maxFileLineCountRule.ts b/src/rules/maxFileLineCountRule.ts index 249c173728f..0479cc969df 100644 --- a/src/rules/maxFileLineCountRule.ts +++ b/src/rules/maxFileLineCountRule.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -40,7 +40,7 @@ export class Rule extends Lint.Rules.AbstractRule { let msg = `This file has ${lineCount} lines, which exceeds the maximum of ${lineLimit} lines allowed. `; msg += `Consider breaking this file up into smaller parts`; return msg; - }; + } public isEnabled(): boolean { if (super.isEnabled()) { diff --git a/src/rules/maxLineLengthRule.ts b/src/rules/maxLineLengthRule.ts index a029f3938a5..d46730c2624 100644 --- a/src/rules/maxLineLengthRule.ts +++ b/src/rules/maxLineLengthRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -41,7 +41,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING_FACTORY = (lineLimit: number) => { return `Exceeds maximum line length of ${lineLimit}`; - }; + } public isEnabled(): boolean { if (super.isEnabled()) { diff --git a/src/rules/memberAccessRule.ts b/src/rules/memberAccessRule.ts index 07f774b3fc9..8fef76d20f3 100644 --- a/src/rules/memberAccessRule.ts +++ b/src/rules/memberAccessRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index 4c3bbf09712..9b4201f4cce 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -16,7 +16,7 @@ */ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; /* start old options */ const OPTION_VARIABLES_BEFORE_FUNCTIONS = "variables-before-functions"; diff --git a/src/rules/newParensRule.ts b/src/rules/newParensRule.ts index 9c774347f06..7feae34434f 100644 --- a/src/rules/newParensRule.ts +++ b/src/rules/newParensRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noAngleBracketTypeAssertionRule.ts b/src/rules/noAngleBracketTypeAssertionRule.ts index dbf8a36069e..ebf381c52a3 100644 --- a/src/rules/noAngleBracketTypeAssertionRule.ts +++ b/src/rules/noAngleBracketTypeAssertionRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noAnyRule.ts b/src/rules/noAnyRule.ts index c8e8db534b2..7346f151838 100644 --- a/src/rules/noAnyRule.ts +++ b/src/rules/noAnyRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noArgRule.ts b/src/rules/noArgRule.ts index e3471baed69..4adb5d727db 100644 --- a/src/rules/noArgRule.ts +++ b/src/rules/noArgRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noBitwiseRule.ts b/src/rules/noBitwiseRule.ts index 62bcd4b0054..bb69045394d 100644 --- a/src/rules/noBitwiseRule.ts +++ b/src/rules/noBitwiseRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noConditionalAssignmentRule.ts b/src/rules/noConditionalAssignmentRule.ts index bf92d769190..89ad7ff898e 100644 --- a/src/rules/noConditionalAssignmentRule.ts +++ b/src/rules/noConditionalAssignmentRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noConsecutiveBlankLinesRule.ts b/src/rules/noConsecutiveBlankLinesRule.ts index 00921213c67..fffa18ee7bf 100644 --- a/src/rules/noConsecutiveBlankLinesRule.ts +++ b/src/rules/noConsecutiveBlankLinesRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { public static DEFAULT_ALLOWED_BLANKS = 1; diff --git a/src/rules/noConsoleRule.ts b/src/rules/noConsoleRule.ts index 92df69e1f72..e024123a764 100644 --- a/src/rules/noConsoleRule.ts +++ b/src/rules/noConsoleRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as BanRule from "./banRule"; export class Rule extends BanRule.Rule { diff --git a/src/rules/noConstructRule.ts b/src/rules/noConstructRule.ts index 5ef72fb2fb9..f687688f0e6 100644 --- a/src/rules/noConstructRule.ts +++ b/src/rules/noConstructRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noDebuggerRule.ts b/src/rules/noDebuggerRule.ts index f6a531f373a..85703395895 100644 --- a/src/rules/noDebuggerRule.ts +++ b/src/rules/noDebuggerRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noDefaultExportRule.ts b/src/rules/noDefaultExportRule.ts index 74fa7f1fa51..ae9c7bcdd05 100644 --- a/src/rules/noDefaultExportRule.ts +++ b/src/rules/noDefaultExportRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noDuplicateVariableRule.ts b/src/rules/noDuplicateVariableRule.ts index c9ac6d72a8c..7dc9473bfdb 100644 --- a/src/rules/noDuplicateVariableRule.ts +++ b/src/rules/noDuplicateVariableRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -38,7 +38,9 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (name: string) => `Duplicate variable: '${name}'`; + public static FAILURE_STRING_FACTORY = (name: string) => { + return `Duplicate variable: '${name}'`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NoDuplicateVariableWalker(sourceFile, this.getOptions())); diff --git a/src/rules/noEmptyRule.ts b/src/rules/noEmptyRule.ts index 8388a361c03..d6dbbe1b695 100644 --- a/src/rules/noEmptyRule.ts +++ b/src/rules/noEmptyRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noEvalRule.ts b/src/rules/noEvalRule.ts index d8e62642bff..a2c4194dc0f 100644 --- a/src/rules/noEvalRule.ts +++ b/src/rules/noEvalRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noForInArrayRule.ts b/src/rules/noForInArrayRule.ts index d9abbde92e0..d8e7f949e1c 100644 --- a/src/rules/noForInArrayRule.ts +++ b/src/rules/noForInArrayRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.TypedRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noInferrableTypesRule.ts b/src/rules/noInferrableTypesRule.ts index 252343ae056..79c918d9cc8 100644 --- a/src/rules/noInferrableTypesRule.ts +++ b/src/rules/noInferrableTypesRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_IGNORE_PARMS = "ignore-params"; @@ -47,7 +47,9 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (type: string) => `LHS type (${type}) inferred by RHS expression, remove type annotation`; + public static FAILURE_STRING_FACTORY = (type: string) => { + return `LHS type (${type}) inferred by RHS expression, remove type annotation`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NoInferrableTypesWalker(sourceFile, this.getOptions())); diff --git a/src/rules/noInternalModuleRule.ts b/src/rules/noInternalModuleRule.ts index bfb4f165a05..751a4e8a75f 100644 --- a/src/rules/noInternalModuleRule.ts +++ b/src/rules/noInternalModuleRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noInvalidThisRule.ts b/src/rules/noInvalidThisRule.ts index d36bc9da871..12e8f2c7ea0 100644 --- a/src/rules/noInvalidThisRule.ts +++ b/src/rules/noInvalidThisRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; interface Scope { inClass: boolean; diff --git a/src/rules/noMergeableNamespaceRule.ts b/src/rules/noMergeableNamespaceRule.ts index be9aa32ef9d..0829b8157c2 100644 --- a/src/rules/noMergeableNamespaceRule.ts +++ b/src/rules/noMergeableNamespaceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noNamespaceRule.ts b/src/rules/noNamespaceRule.ts index 9152f9496c3..0113eb1d8dd 100644 --- a/src/rules/noNamespaceRule.ts +++ b/src/rules/noNamespaceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noNullKeywordRule.ts b/src/rules/noNullKeywordRule.ts index d83d467aad7..6592c1640d1 100644 --- a/src/rules/noNullKeywordRule.ts +++ b/src/rules/noNullKeywordRule.ts @@ -19,7 +19,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noParameterPropertiesRule.ts b/src/rules/noParameterPropertiesRule.ts index 78e04d0f47e..d4ee70be800 100644 --- a/src/rules/noParameterPropertiesRule.ts +++ b/src/rules/noParameterPropertiesRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -35,7 +35,9 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (ident: string) => `Property '${ident}' cannot be declared in the constructor`; + public static FAILURE_STRING_FACTORY = (ident: string) => { + return `Property '${ident}' cannot be declared in the constructor`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NoParameterPropertiesWalker(sourceFile, this.getOptions())); diff --git a/src/rules/noReferenceRule.ts b/src/rules/noReferenceRule.ts index a35b5461957..e20e38744c3 100644 --- a/src/rules/noReferenceRule.ts +++ b/src/rules/noReferenceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noRequireImportsRule.ts b/src/rules/noRequireImportsRule.ts index 051dc8fe221..45031cc4fc8 100644 --- a/src/rules/noRequireImportsRule.ts +++ b/src/rules/noRequireImportsRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noShadowedVariableRule.ts b/src/rules/noShadowedVariableRule.ts index d78a8eed1ec..6b2ffa0057e 100644 --- a/src/rules/noShadowedVariableRule.ts +++ b/src/rules/noShadowedVariableRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -33,7 +33,9 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (name: string) => `Shadowed variable: '${name}'`; + public static FAILURE_STRING_FACTORY = (name: string) => { + return `Shadowed variable: '${name}'`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NoShadowedVariableWalker(sourceFile, this.getOptions())); diff --git a/src/rules/noStringLiteralRule.ts b/src/rules/noStringLiteralRule.ts index 5fb944d8125..237ce27f94a 100644 --- a/src/rules/noStringLiteralRule.ts +++ b/src/rules/noStringLiteralRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noSwitchCaseFallThroughRule.ts b/src/rules/noSwitchCaseFallThroughRule.ts index 2f83c0ec3f9..29b8cc7b678 100644 --- a/src/rules/noSwitchCaseFallThroughRule.ts +++ b/src/rules/noSwitchCaseFallThroughRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index b102a612388..51cc186c4f2 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noUnreachableRule.ts b/src/rules/noUnreachableRule.ts deleted file mode 100644 index 2d63de4d65a..00000000000 --- a/src/rules/noUnreachableRule.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @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 "../lint"; - -export class Rule extends Lint.Rules.AbstractRule { - /* tslint:disable:object-literal-sort-keys */ - public static metadata: Lint.IRuleMetadata = { - ruleName: "no-unreachable", - description: "Disallows unreachable code after `break`, `catch`, `throw`, and `return` statements.", - rationale: "Unreachable code is often indication of a logic error.", - optionsDescription: "Not configurable.", - options: null, - optionExamples: ["true"], - type: "functionality", - typescriptOnly: false, - }; - /* tslint:enable:object-literal-sort-keys */ - - public static FAILURE_STRING = "unreachable code"; - - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithWalker(new NoUnreachableWalker(sourceFile, this.getOptions())); - } -} - -class NoUnreachableWalker extends Lint.RuleWalker { - private hasReturned: boolean; - - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { - super(sourceFile, options); - this.hasReturned = false; - } - - public visitNode(node: ts.Node) { - const previousReturned = this.hasReturned; - // function declarations and type alias declarations can be hoisted - // -- so set hasReturned to false until we're done with the function - if (node.kind === ts.SyntaxKind.FunctionDeclaration || node.kind === ts.SyntaxKind.TypeAliasDeclaration) { - this.hasReturned = false; - } - - if (this.hasReturned) { - this.hasReturned = false; - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING)); - } - - super.visitNode(node); - - // if there is further code after the hoisted function and we returned before that code is unreachable - // so reset hasReturned to its previous state to check for that - if (node.kind === ts.SyntaxKind.FunctionDeclaration || node.kind === ts.SyntaxKind.TypeAliasDeclaration) { - this.hasReturned = previousReturned; - } - } - - public visitBlock(node: ts.Block) { - super.visitBlock(node); - this.hasReturned = false; - } - - public visitCaseClause(node: ts.CaseClause) { - super.visitCaseClause(node); - this.hasReturned = false; - } - - public visitDefaultClause(node: ts.DefaultClause) { - super.visitDefaultClause(node); - this.hasReturned = false; - } - - public visitIfStatement(node: ts.IfStatement) { - this.visitNode(node.expression); - this.visitNode(node.thenStatement); - this.hasReturned = false; - if (node.elseStatement != null) { - this.visitNode(node.elseStatement); - this.hasReturned = false; - } - } - - public visitBreakStatement(node: ts.BreakOrContinueStatement) { - super.visitBreakStatement(node); - this.hasReturned = true; - } - - public visitContinueStatement(node: ts.BreakOrContinueStatement) { - super.visitContinueStatement(node); - this.hasReturned = true; - } - - public visitReturnStatement(node: ts.ReturnStatement) { - super.visitReturnStatement(node); - this.hasReturned = true; - } - - public visitThrowStatement(node: ts.ThrowStatement) { - super.visitThrowStatement(node); - this.hasReturned = true; - } -} diff --git a/src/rules/noUnsafeFinallyRule.ts b/src/rules/noUnsafeFinallyRule.ts index 9c88031cfff..71339452a89 100644 --- a/src/rules/noUnsafeFinallyRule.ts +++ b/src/rules/noUnsafeFinallyRule.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -40,14 +40,12 @@ export class Rule extends Lint.Rules.AbstractRule { /* tslint:enable:object-literal-sort-keys */ public static FAILURE_TYPE_BREAK = "break"; - public static FAILURE_TYPE_CONTINUE = "continue"; - public static FAILURE_TYPE_RETURN = "return"; - public static FAILURE_TYPE_THROW = "throw"; - - public static FAILURE_STRING_FACTORY = (name: string) => `${name} statements in finally blocks are forbidden.`; + public static FAILURE_STRING_FACTORY = (name: string) => { + return `${name} statements in finally blocks are forbidden.`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new NoReturnInFinallyScopeAwareWalker(sourceFile, this.getOptions())); diff --git a/src/rules/noUnusedExpressionRule.ts b/src/rules/noUnusedExpressionRule.ts index ae863c0b3af..83c51a222c9 100644 --- a/src/rules/noUnusedExpressionRule.ts +++ b/src/rules/noUnusedExpressionRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noUnusedNewRule.ts b/src/rules/noUnusedNewRule.ts index a5cfba4b5ea..283e3ac57bd 100644 --- a/src/rules/noUnusedNewRule.ts +++ b/src/rules/noUnusedNewRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; import { NoUnusedExpressionWalker } from "./noUnusedExpressionRule"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index 741e4e3fd46..a39cbe98516 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_REACT = "react"; const OPTION_CHECK_PARAMETERS = "check-parameters"; @@ -74,8 +74,9 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_TYPE_PARAM = "parameter"; public static FAILURE_TYPE_PROP = "property"; public static FAILURE_TYPE_VAR = "variable"; - - public static FAILURE_STRING_FACTORY = (type: string, name: string) => `Unused ${type}: '${name}'`; + public static FAILURE_STRING_FACTORY = (type: string, name: string) => { + return `Unused ${type}: '${name}'`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const languageService = Lint.createLanguageService(sourceFile.fileName, sourceFile.getFullText()); @@ -418,7 +419,7 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { private fail(type: string, name: string, position: number, replacements?: Lint.Replacement[]) { let fix: Lint.Fix; if (replacements && replacements.length) { - fix = new Lint.Fix("no-unused-variable", replacements); + fix = new Lint.Fix(Rule.metadata.ruleName, replacements); } this.addFailure(this.createFailure(position, name.length, Rule.FAILURE_STRING_FACTORY(type, name), fix)); } diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index 8654658a8ae..a173360422c 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/noVarKeywordRule.ts b/src/rules/noVarKeywordRule.ts index 6b285918cfb..ae1bb2fa8e0 100644 --- a/src/rules/noVarKeywordRule.ts +++ b/src/rules/noVarKeywordRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -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/noVarRequiresRule.ts b/src/rules/noVarRequiresRule.ts index b13bc5824da..ac5ada9913a 100644 --- a/src/rules/noVarRequiresRule.ts +++ b/src/rules/noVarRequiresRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/objectLiteralKeyQuotesRule.ts b/src/rules/objectLiteralKeyQuotesRule.ts index c63dac4e59a..2058d83d136 100644 --- a/src/rules/objectLiteralKeyQuotesRule.ts +++ b/src/rules/objectLiteralKeyQuotesRule.ts @@ -1,4 +1,21 @@ -import * as Lint from "../lint"; +/** + * @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 Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -28,13 +45,15 @@ 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 + property names may be quoted. For ES6, computed property names (\`{[name]: value}\`) and methods (\`{foo() {}}\`) never need to be quoted.`, options: { type: "string", - enum: ["always", "as-needed"], - // TODO: eslint also supports "consistent", "consistent-as-needed" modes. + enum: ["always", "as-needed", "consistent", "consistent-as-needed"], // TODO: eslint supports "keywords", "unnecessary" and "numbers" options. }, optionExamples: ["[true, \"as-needed\"]", "[true, \"always\"]"], @@ -43,8 +62,13 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static UNNEEDED_QUOTES = (name: string) => `Unnecessarily quoted property '${name}' found.`; - public static UNQUOTED_PROPERTY = (name: string) => `Unquoted property '${name}' found.`; + public static INCONSISTENT_PROPERTY = `All property names in this object literal must be consistently quoted or unquoted.`; + public static UNNEEDED_QUOTES = (name: string) => { + return `Unnecessarily quoted property '${name}' found.`; + } + public static UNQUOTED_PROPERTY = (name: string) => { + return `Unquoted property '${name}' found.`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { const objectLiteralKeyQuotesWalker = new ObjectLiteralKeyQuotesWalker(sourceFile, this.getOptions()); @@ -54,13 +78,21 @@ 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 NUMBER_REGEX = /^[0-9]+$/; - -type QuotesMode = "always" | "as-needed"; +type QuotesMode = "always" | "as-needed" | "consistent" | "consistent-as-needed"; + +interface IObjectLiteralState { + // potential failures for properties that have quotes but don't need them + quotesNotNeededProperties: Lint.RuleFailure[]; + // potential failures for properties that don't have quotes + unquotedProperties: Lint.RuleFailure[]; + // whether or not any of the properties require quotes + hasQuotesNeededProperty: boolean; +} class ObjectLiteralKeyQuotesWalker extends Lint.RuleWalker { private mode: QuotesMode; + private currentState: IObjectLiteralState; constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); @@ -70,27 +102,59 @@ class ObjectLiteralKeyQuotesWalker extends Lint.RuleWalker { public visitPropertyAssignment(node: ts.PropertyAssignment) { const name = node.name; - if (this.mode === "always") { - if (name.kind !== ts.SyntaxKind.StringLiteral && - name.kind !== ts.SyntaxKind.ComputedPropertyName) { - this.addFailure(this.createFailure(name.getStart(), name.getWidth(), - Rule.UNQUOTED_PROPERTY(name.getText()))); - } - } else if (this.mode === "as-needed") { - if (name.kind === ts.SyntaxKind.StringLiteral) { - // Check if the quoting is necessary. - const stringNode = name as ts.StringLiteral; - const property = stringNode.text; - - const isIdentifier = IDENTIFIER_NAME_REGEX.test(property); - const isNumber = NUMBER_REGEX.test(property); - if (isIdentifier || (isNumber && Number(property).toString() === property)) { - this.addFailure(this.createFailure(stringNode.getStart(), stringNode.getWidth(), - Rule.UNNEEDED_QUOTES(property))); - } + if (name.kind !== ts.SyntaxKind.StringLiteral && + name.kind !== ts.SyntaxKind.ComputedPropertyName) { + + const errorText = Rule.UNQUOTED_PROPERTY(name.getText()); + this.currentState.unquotedProperties.push(this.createFailure(name.getStart(), name.getWidth(), errorText)); + } + if (name.kind === ts.SyntaxKind.StringLiteral) { + // Check if the quoting is necessary. + const stringNode = name as ts.StringLiteral; + const property = stringNode.text; + + const isIdentifier = IDENTIFIER_NAME_REGEX.test(property); + const isNumber = NUMBER_REGEX.test(property); + if (isIdentifier || (isNumber && Number(property).toString() === property)) { + const errorText = Rule.UNNEEDED_QUOTES(property); + const failure = this.createFailure(stringNode.getStart(), stringNode.getWidth(), errorText); + this.currentState.quotesNotNeededProperties.push(failure); + } else { + this.currentState.hasQuotesNeededProperty = true; } } super.visitPropertyAssignment(node); } + + public visitObjectLiteralExpression(node: ts.ObjectLiteralExpression) { + let state: IObjectLiteralState = { + hasQuotesNeededProperty: false, + quotesNotNeededProperties: [], + unquotedProperties: [], + }; + // a nested object literal should store its parent state to restore when finished + let previousState = this.currentState; + this.currentState = state; + + super.visitObjectLiteralExpression(node); + + if (this.mode === "always" || (this.mode === "consistent-as-needed" && state.hasQuotesNeededProperty)) { + for (const failure of state.unquotedProperties) { + this.addFailure(failure); + } + } else if (this.mode === "as-needed" || (this.mode === "consistent-as-needed" && !state.hasQuotesNeededProperty)) { + for (const failure of state.quotesNotNeededProperties) { + this.addFailure(failure); + } + } else if (this.mode === "consistent") { + 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.currentState = previousState; + } } diff --git a/src/rules/objectLiteralShorthandRule.ts b/src/rules/objectLiteralShorthandRule.ts index 6f58862c687..ed59daa3828 100644 --- a/src/rules/objectLiteralShorthandRule.ts +++ b/src/rules/objectLiteralShorthandRule.ts @@ -1,4 +1,4 @@ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -6,6 +6,7 @@ export class Rule extends Lint.Rules.AbstractRule { public static metadata: Lint.IRuleMetadata = { ruleName: "object-literal-shorthand", description: "Enforces use of ES6 object literal shorthand when possible.", + optionsDescription: "Not configurable.", options: null, optionExamples: ["true"], type: "style", diff --git a/src/rules/objectLiteralSortKeysRule.ts b/src/rules/objectLiteralSortKeysRule.ts index 5ff902585eb..78dc7d96efa 100644 --- a/src/rules/objectLiteralSortKeysRule.ts +++ b/src/rules/objectLiteralSortKeysRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -33,7 +33,9 @@ export class Rule extends Lint.Rules.AbstractRule { }; /* tslint:enable:object-literal-sort-keys */ - public static FAILURE_STRING_FACTORY = (name: string) => `The key '${name}' is not sorted alphabetically`; + public static FAILURE_STRING_FACTORY = (name: string) => { + return `The key '${name}' is not sorted alphabetically`; + } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { return this.applyWithWalker(new ObjectLiteralSortKeysWalker(sourceFile, this.getOptions())); diff --git a/src/rules/oneLineRule.ts b/src/rules/oneLineRule.ts index da887a4916a..bad0235d9d4 100644 --- a/src/rules/oneLineRule.ts +++ b/src/rules/oneLineRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_BRACE = "check-open-brace"; const OPTION_CATCH = "check-catch"; diff --git a/src/rules/oneVariablePerDeclarationRule.ts b/src/rules/oneVariablePerDeclarationRule.ts index 409ef4286ed..f8c056ba4e9 100644 --- a/src/rules/oneVariablePerDeclarationRule.ts +++ b/src/rules/oneVariablePerDeclarationRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_IGNORE_FOR_LOOP = "ignore-for-loop"; diff --git a/src/rules/onlyArrowFunctionsRule.ts b/src/rules/onlyArrowFunctionsRule.ts index b2306b990ff..5d144558884 100644 --- a/src/rules/onlyArrowFunctionsRule.ts +++ b/src/rules/onlyArrowFunctionsRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_ALLOW_DECLARATIONS = "allow-declarations"; diff --git a/src/rules/orderedImportsRule.ts b/src/rules/orderedImportsRule.ts index f10eb5941ac..b241f212e21 100644 --- a/src/rules/orderedImportsRule.ts +++ b/src/rules/orderedImportsRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -110,6 +110,36 @@ function findUnsortedPair(xs: ts.Node[], transform: (x: string) => string): [ts. return null; } +function compare(a: string, b: string) { + const isLow = (value: string) => { + return [".", "/"].some((x) => value[0] === x); + }; + if (isLow(a) && !isLow(b)) { + return 1; + } else if (!isLow(a) && isLow(b)) { + return -1; + } else if (a > b) { + return 1; + } else if (a < b) { + return -1; + } + return 0; +} + +function removeQuotes(value: string) { + // strip out quotes + if (value && value.length > 1 && (value[0] === "'" || value[0] === "\"")) { + value = value.substr(1, value.length - 2); + } + return value; +} + +function sortByKey(xs: T[], getSortKey: (x: T) => string): T[] { + return xs.slice().sort((a, b) => { + return compare(getSortKey(a), getSortKey(b)); + }); +} + // 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} = { @@ -120,8 +150,9 @@ const TRANSFORMS: {[ordering: string]: (x: string) => string} = { }; class OrderedImportsWalker extends Lint.RuleWalker { - // This gets reset after every blank line. - private lastImportSource: string = null; + private currentImportsBlock: ImportsBlock = new ImportsBlock(); + // keep a reference to the last Fix object so when the entire block is replaced, the replacement can be added + private lastFix: Lint.Fix; private importSourcesOrderTransform: (x: string) => string = null; private namedImportsOrderTransform: (x: string) => string = null; @@ -137,13 +168,17 @@ class OrderedImportsWalker extends Lint.RuleWalker { // e.g. "import Foo from "./foo";" public visitImportDeclaration(node: ts.ImportDeclaration) { - const source = this.importSourcesOrderTransform(node.moduleSpecifier.getText()); + let source = node.moduleSpecifier.getText(); + source = removeQuotes(source); + source = this.importSourcesOrderTransform(source); + const previousSource = this.currentImportsBlock.getLastImportSource(); + this.currentImportsBlock.addImportDeclaration(node, source); - if (this.lastImportSource && source < this.lastImportSource) { - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), - Rule.IMPORT_SOURCES_UNORDERED)); + 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.lastImportSource = source; super.visitImportDeclaration(node); } @@ -156,25 +191,135 @@ class OrderedImportsWalker extends Lint.RuleWalker { const pair = findUnsortedPair(imports, this.namedImportsOrderTransform); if (pair !== null) { const [a, b] = pair; - this.addFailure( - this.createFailure( - a.getStart(), - b.getEnd() - a.getStart(), - Rule.NAMED_IMPORTS_UNORDERED)); + const sortedDeclarations = sortByKey(imports, (x) => this.namedImportsOrderTransform(x.getText())).map((x) => x.getText()); + // replace in reverse order to preserve earlier offsets + for (let i = imports.length - 1; i >= 0; i--) { + const start = imports[i].getStart(); + const length = imports[i].getText().length; + + // replace the named imports one at a time to preserve whitespace + this.currentImportsBlock.replaceNamedImports(start, length, sortedDeclarations[i]); + } + + 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); } super.visitNamedImports(node); } - // Check for a blank line, in which case we should reset the import ordering. + // keep reading the block of import declarations until the block ends, then replace the entire block + // this allows the reorder of named imports to work well with reordering lines public visitNode(node: ts.Node) { const prefixLength = node.getStart() - node.getFullStart(); const prefix = node.getFullText().slice(0, prefixLength); + const hasBlankLine = prefix.indexOf("\n\n") >= 0 || prefix.indexOf("\r\n\r\n") >= 0; + const notImportDeclaration = node.parent != null + && node.parent.kind === ts.SyntaxKind.SourceFile + && node.kind !== ts.SyntaxKind.ImportDeclaration; - if (prefix.indexOf("\n\n") >= 0 || - prefix.indexOf("\r\n\r\n") >= 0) { - this.lastImportSource = null; + if (hasBlankLine || notImportDeclaration) { + // end of block + if (this.lastFix != null) { + const replacement = this.currentImportsBlock.getReplacement(); + if (replacement != null) { + this.lastFix.replacements.push(replacement); + } + this.lastFix = null; + } + this.currentImportsBlock = new ImportsBlock(); } super.visitNode(node); } } + +interface ImportDeclaration { + node: ts.ImportDeclaration; + nodeEndOffset: number; // end position of node within source file + nodeStartOffset: number; // start position of node within source file + text: string; // initialized with original import text; modified if the named imports are reordered + sourcePath: string; +} + +class ImportsBlock { + private importDeclarations: ImportDeclaration[] = []; + + public addImportDeclaration(node: ts.ImportDeclaration, sourcePath: string) { + const start = this.getStartOffset(node); + const end = this.getEndOffset(node); + const text = node.getSourceFile().text.substring(start, end); + + if (start > node.getStart() || end === 0) { + // skip block if any statements don't end with a newline to simplify implementation + this.importDeclarations = []; + return; + } + + this.importDeclarations.push({ + node, + nodeEndOffset: end, + nodeStartOffset: start, + sourcePath, + text, + }); + } + + // 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) { + // nothing to replace. This can happen if the block is skipped + return; + } + + const start = fileOffset - importDeclaration.nodeStartOffset; + if (start < 0 || start + length > importDeclaration.node.getEnd()) { + throw "Unexpected named import position"; + } + + const initialText = importDeclaration.text; + importDeclaration.text = initialText.substring(0, start) + replacement + initialText.substring(start + length); + } + + public getLastImportSource() { + if (this.importDeclarations.length === 0) { + return null; + } + return this.getLastImportDeclaration().sourcePath; + } + + // creates a Lint.Replacement object with ordering fixes for the entire block + public getReplacement() { + if (this.importDeclarations.length === 0) { + return null; + } + const sortedDeclarations = sortByKey(this.importDeclarations.slice(), (x) => x.sourcePath); + const fixedText = sortedDeclarations.map((x) => x.text).join(""); + const start = this.importDeclarations[0].nodeStartOffset; + const end = this.getLastImportDeclaration().nodeEndOffset; + return new Lint.Replacement(start, end - start, fixedText); + } + + // 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(); + } + return this.getLastImportDeclaration().nodeEndOffset; + } + + // gets the offset of the end of the import's line, including newline, to include comment to the right + private getEndOffset(node: ts.ImportDeclaration) { + let endLineOffset = node.getSourceFile().text.indexOf("\n", node.end) + 1; + return endLineOffset; + } + + private getLastImportDeclaration() { + return this.importDeclarations[this.importDeclarations.length - 1]; + } +} diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index cd1078cfb31..68183ea3c49 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as Lint from "../lint"; +import * as Lint from "../index"; import * as ts from "typescript"; export class Rule extends Lint.Rules.AbstractRule { @@ -28,6 +28,7 @@ export class Rule extends Lint.Rules.AbstractRule { options: null, optionExamples: ["true"], type: "typescript", + typescriptOnly: false, }; /* tslint:enable:object-literal-sort-keys */ diff --git a/src/rules/quotemarkRule.ts b/src/rules/quotemarkRule.ts index a3b63c3b826..ebfb7bda6f3 100644 --- a/src/rules/quotemarkRule.ts +++ b/src/rules/quotemarkRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; enum QuoteMark { SINGLE_QUOTES, diff --git a/src/rules/radixRule.ts b/src/rules/radixRule.ts index 28174d603f4..e35521fc226 100644 --- a/src/rules/radixRule.ts +++ b/src/rules/radixRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/restrictPlusOperandsRule.ts b/src/rules/restrictPlusOperandsRule.ts index 68cd410a967..05becd841fa 100644 --- a/src/rules/restrictPlusOperandsRule.ts +++ b/src/rules/restrictPlusOperandsRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.TypedRule { /* tslint:disable:object-literal-sort-keys */ @@ -34,7 +34,9 @@ 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) => `cannot add type ${type}`; + public static UNSUPPORTED_TYPE_FAILURE_FACTORY = (type: string) => { + return `cannot add type ${type}`; + } public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { return this.applyWithWalker(new RestrictPlusOperandsWalker(sourceFile, this.getOptions(), program)); diff --git a/src/rules/semicolonRule.ts b/src/rules/semicolonRule.ts index a175cf7b69a..b9be0599be4 100644 --- a/src/rules/semicolonRule.ts +++ b/src/rules/semicolonRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_ALWAYS = "always"; const OPTION_NEVER = "never"; @@ -121,7 +121,9 @@ class SemicolonWalker extends Lint.RuleWalker { public visitPropertyDeclaration(node: ts.PropertyDeclaration) { const initializer = node.initializer; - if (initializer && initializer.kind === ts.SyntaxKind.ArrowFunction) { + + // check if this is a multi-line arrow function (`[^]` in the regex matches all characters including CR & LF) + if (initializer && initializer.kind === ts.SyntaxKind.ArrowFunction && /\{[^]*\n/.test(node.getText())) { if (!this.hasOption(OPTION_IGNORE_BOUND_CLASS_METHODS)) { this.checkSemicolonAt(node, "never"); } diff --git a/src/rules/switchDefaultRule.ts b/src/rules/switchDefaultRule.ts index fb9f9d50419..0d8524c9c61 100644 --- a/src/rules/switchDefaultRule.ts +++ b/src/rules/switchDefaultRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 42514e14e5f..35d50dd61bd 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/tripleEqualsRule.ts b/src/rules/tripleEqualsRule.ts index 67d4dc7e62a..24b04cd9622 100644 --- a/src/rules/tripleEqualsRule.ts +++ b/src/rules/tripleEqualsRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_ALLOW_NULL_CHECK = "allow-null-check"; const OPTION_ALLOW_UNDEFINED_CHECK = "allow-undefined-check"; diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index 6c4a7f0c4e6..7e0f5b7dce0 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/typedefWhitespaceRule.ts b/src/rules/typedefWhitespaceRule.ts index 595ca940086..3713d9db65d 100644 --- a/src/rules/typedefWhitespaceRule.ts +++ b/src/rules/typedefWhitespaceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; /* tslint:disable:object-literal-sort-keys */ const SPACE_OPTIONS = { diff --git a/src/rules/useIsnanRule.ts b/src/rules/useIsnanRule.ts index d0902c2fa34..651b30f4265 100644 --- a/src/rules/useIsnanRule.ts +++ b/src/rules/useIsnanRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/variableNameRule.ts b/src/rules/variableNameRule.ts index 18028d786f6..77165f54efa 100644 --- a/src/rules/variableNameRule.ts +++ b/src/rules/variableNameRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const BANNED_KEYWORDS = ["any", "Number", "number", "String", "string", "Boolean", "boolean", "Undefined", "undefined"]; diff --git a/src/rules/whitespaceRule.ts b/src/rules/whitespaceRule.ts index 63465e70912..3e9bc272042 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import * as Lint from "../lint"; +import * as Lint from "../index"; const OPTION_BRANCH = "check-branch"; const OPTION_DECL = "check-decl"; diff --git a/src/test.ts b/src/test.ts index 77d060794c4..de9f71d133d 100644 --- a/src/test.ts +++ b/src/test.ts @@ -24,9 +24,9 @@ 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"; -import * as Linter from "./tslint"; const MARKUP_FILE_EXTENSION = ".lint"; const FIXES_FILE_EXTENSION = ".fix"; @@ -47,7 +47,7 @@ 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); + const tslintConfig = Linter.findConfiguration(path.join(testDirectory, "tslint.json"), null).results; const results: TestResult = { directory: testDirectory, results: {} }; for (const fileToLint of filesToLint) { @@ -86,13 +86,14 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ } const lintOptions = { - configuration: tslintConfig, + fix: false, formatter: "prose", formattersDirectory: "", rulesDirectory, }; - const linter = new Linter(fileBasename, fileTextWithoutMarkup, lintOptions, program); - const failures = linter.lint().failures; + const linter = new Linter(lintOptions, program); + linter.lint(fileBasename, fileTextWithoutMarkup, tslintConfig); + const failures = linter.getResult().failures; const errorsFromLinter: LintError[] = failures.map((failure) => { const startLineAndCharacter = failure.getStartPosition().getLineAndCharacter(); const endLineAndCharacter = failure.getEndPosition().getLineAndCharacter(); diff --git a/src/test/lintError.ts b/src/test/lintError.ts index 6b04e7b080a..46968be2599 100644 --- a/src/test/lintError.ts +++ b/src/test/lintError.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { Error } from "../configuration"; + export interface PositionInFile { line: number; col: number; @@ -40,5 +42,5 @@ export function errorComparator(err1: LintError, err2: LintError) { } export function lintSyntaxError(message: string) { - return new Error(`Lint File Syntax Error: ${message}`); + return new Error(`Lint File Syntax Error: ${message}`) as Error; } diff --git a/src/tsconfig.json b/src/tsconfig.json index 797e7d67879..82843d7dc6f 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,5 +1,5 @@ { - "version": "2.0.3", + "version": "2.0.10", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index f358dc672f3..ab8d3d259c1 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -26,8 +26,9 @@ import { DEFAULT_CONFIG, findConfiguration, } from "./configuration"; -import {consoleTestResultHandler, runTest} from "./test"; -import * as Linter from "./tslintMulti"; +import * as Linter from "./linter"; +import { consoleTestResultHandler, runTest } from "./test"; +import { updateNotifierCheck } from "./updateNotifier"; let processed = optimist .usage("Usage: $0 [options] file ...") @@ -49,6 +50,11 @@ let processed = optimist e: { alias: "exclude", describe: "exclude globs from path expansion", + type: "string", + }, + fix: { + describe: "Fixes linting errors for select rules. This may overwrite linted files", + type: "boolean", }, force: { describe: "return status code 0 even if there are lint errors", @@ -152,6 +158,9 @@ tslint accepts the following commandline options: This option can be supplied multiple times if you need multiple globs to indicate which files to exclude. + --fix: + Fixes linting errors for select rules. This may overwrite linted files. + --force: Return status code 0 even if there are any lint errors. Useful while running as npm script. @@ -184,7 +193,7 @@ tslint accepts the following commandline options: formatters are prose (human readable), json (machine readable) and verbose. prose is the default if this option is not used. Other built-in options include pmd, msbuild, checkstyle, and vso. - Additonal formatters can be added and used if the --formatters-dir + Additional formatters can be added and used if the --formatters-dir option is set. --test: @@ -220,6 +229,7 @@ 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 || "", @@ -248,8 +258,14 @@ const processFiles = (files: string[], program?: ts.Program) => { } const contents = fs.readFileSync(file, "utf8"); - const configuration = findConfiguration(possibleConfigAbsolutePath, file); - linter.lint(file, contents, configuration); + 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); + } } const lintResult = linter.getResult(); @@ -259,6 +275,11 @@ const processFiles = (files: string[], program?: ts.Program) => { process.exit(argv.force ? 0 : 2); } }); + + if (lintResult.format === "prose") { + // Check to see if there are any updates available + updateNotifierCheck(); + } }; // if both files and tsconfig are present, use files @@ -296,7 +317,19 @@ if (argv.project != null) { } } +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 - .map((file: string) => glob.sync(file, { ignore: argv.e, nodir: true })) - .reduce((a: string[], b: string[]) => a.concat(b)); + // 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)); + processFiles(files, program); diff --git a/src/tslint.ts b/src/tslint.ts deleted file mode 100644 index 2c91e51a192..00000000000 --- a/src/tslint.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @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 { - DEFAULT_CONFIG, - findConfiguration, - findConfigurationPath, - getRulesDirectories, - loadConfigurationFromPath, -} from "./configuration"; -import { ILinterOptions, ILinterOptionsRaw, LintResult } from "./lint"; -import * as MultiLinter from "./tslintMulti"; -import { arrayify } from "./utils"; - -/** - * Linter that can lint exactly one file. - */ -class Linter { - public static VERSION = MultiLinter.VERSION; - - public static findConfiguration = findConfiguration; - public static findConfigurationPath = findConfigurationPath; - public static getRulesDirectories = getRulesDirectories; - public static loadConfigurationFromPath = loadConfigurationFromPath; - - private options: ILinterOptions; - - /** - * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. - */ - public static createProgram(configFile: string, projectDirectory?: string): ts.Program { - return MultiLinter.createProgram(configFile, projectDirectory); - } - - /** - * Returns a list of source file names from a TypeScript program. This includes all referenced - * files and excludes declaration (".d.ts") files. - */ - public static getFileNames(program: ts.Program): string[] { - return MultiLinter.getFileNames(program); - } - - constructor(private fileName: string, - private source: string, - options: ILinterOptionsRaw, - private program?: ts.Program) { - this.options = this.computeFullOptions(options); - } - - public lint(): LintResult { - const multiLinter: MultiLinter = new MultiLinter(this.options, this.program); - multiLinter.lint(this.fileName, this.source, this.options.configuration); - return multiLinter.getResult(); - } - - private computeFullOptions(options: ILinterOptionsRaw = {}): ILinterOptions { - if (typeof options !== "object") { - throw new Error("Unknown Linter options type: " + typeof options); - } - - let { configuration, formatter, formattersDirectory, rulesDirectory } = options; - - return { - configuration: configuration || DEFAULT_CONFIG, - formatter: formatter || "prose", - formattersDirectory, - rulesDirectory: arrayify(rulesDirectory).concat(arrayify(configuration.rulesDirectory)), - }; - } -} - -// tslint:disable-next-line:no-namespace -namespace Linter {} - -export = Linter; diff --git a/src/updateNotifier.ts b/src/updateNotifier.ts new file mode 100644 index 00000000000..c6ee143192e --- /dev/null +++ b/src/updateNotifier.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. + */ + +// tslint:disable-next-line:no-var-requires +const updateNotifier = require("update-notifier"); +// tslint:disable-next-line:no-var-requires + +export function updateNotifierCheck(): void { + try { + const pkg = require("../package.json"); + // Check every 3 days for a new version + const cacheTime: number = 1000 * 60 * 60 * 24 * 3; + const changeLogUrl: string = "https://github.com/palantir/tslint/blob/master/CHANGELOG.md"; + const notifier = updateNotifier({ + pkg, + updateCheckInterval: cacheTime, + }); + + if (notifier.notify && notifier.update) { + let message: string = `TSLint update available v${notifier.update.current} → v${notifier.update.latest} \n See ${changeLogUrl}`; + notifier.notify({ message }); + } + } catch (error) { + // ignore error + } +}; diff --git a/src/utils.ts b/src/utils.ts index 20743afd41a..c8b58e36c79 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,6 +15,9 @@ * limitations under the License. */ +/** + * Enforces the invariant that the input is an array. + */ export function arrayify(arg: T | T[]): T[] { if (Array.isArray(arg)) { return arg; @@ -25,6 +28,9 @@ export function arrayify(arg: T | T[]): T[] { } } +/** + * Enforces the invariant that the input is an object. + */ export function objectify(arg: any): any { if (typeof arg === "object" && arg != null) { return arg; diff --git a/test/config/tslint-almost-empty.json b/test/config/tslint-almost-empty.json index 5f229f5ea27..74f5ff48f51 100644 --- a/test/config/tslint-almost-empty.json +++ b/test/config/tslint-almost-empty.json @@ -1 +1 @@ -{ "rules": {} } +{ "jsRules": {}, "rules": {} } diff --git a/test/config/tslint-custom-rules-with-dir.json b/test/config/tslint-custom-rules-with-dir.json index abe7c97a534..8c4ae31b1e5 100644 --- a/test/config/tslint-custom-rules-with-dir.json +++ b/test/config/tslint-custom-rules-with-dir.json @@ -1,5 +1,8 @@ { "rulesDirectory": "../files/custom-rules/", + "jsRules": { + "always-fail": true + }, "rules": { "always-fail": true } diff --git a/test/config/tslint-custom-rules-with-two-dirs.json b/test/config/tslint-custom-rules-with-two-dirs.json index f02d738225e..a43cf3db773 100644 --- a/test/config/tslint-custom-rules-with-two-dirs.json +++ b/test/config/tslint-custom-rules-with-two-dirs.json @@ -1,7 +1,13 @@ { "rulesDirectory": ["../files/custom-rules-2", "../files/custom-rules/"], + "jsRules": { + "always-fail": true, + "no-fail": true, + "rule-two": true + }, "rules": { "always-fail": true, - "no-fail": true + "no-fail": true, + "rule-two": true } } diff --git a/test/config/tslint-custom-rules.json b/test/config/tslint-custom-rules.json index c6568e1ad90..250eafcd974 100644 --- a/test/config/tslint-custom-rules.json +++ b/test/config/tslint-custom-rules.json @@ -1,4 +1,7 @@ { + "jsRules": { + "always-fail": true + }, "rules": { "always-fail": true } diff --git a/test/config/tslint-invalid.json b/test/config/tslint-invalid.json new file mode 100644 index 00000000000..765e0c1d55e --- /dev/null +++ b/test/config/tslint-invalid.json @@ -0,0 +1,2 @@ +{ + \ No newline at end of file diff --git a/test/config/tslint-with-bom.json b/test/config/tslint-with-bom.json index 767e6c5b7e9..2153257c887 100644 --- a/test/config/tslint-with-bom.json +++ b/test/config/tslint-with-bom.json @@ -1 +1 @@ -{ "rules": { } } \ No newline at end of file +{ "jsRules": { }, "rules": { } } \ No newline at end of file diff --git a/test/config/tslint-with-comments.json b/test/config/tslint-with-comments.json index 0990b5256da..ef12ab77da1 100644 --- a/test/config/tslint-with-comments.json +++ b/test/config/tslint-with-comments.json @@ -1,4 +1,13 @@ { + // a nice comment + "jsRules": { + /* + "rule-one": true, + */ + "rule-two": true, + "rule-three": "//not a comment", + "rule-four": "/*also not a comment*/" + }, // a nice comment "rules": { /* diff --git a/test/configurationTests.ts b/test/configurationTests.ts index d7f24d0b201..1a4d28eda2a 100644 --- a/test/configurationTests.ts +++ b/test/configurationTests.ts @@ -15,15 +15,15 @@ */ import * as fs from "fs"; -import * as os from "os"; -import * as path from "path"; -import {IConfigurationFile, extendConfigurationFile, loadConfigurationFromPath} from "../src/configuration"; +import { IConfigurationFile, extendConfigurationFile, loadConfigurationFromPath } from "../src/configuration"; +import { createTempFile } from "./utils"; describe("Configuration", () => { it("extendConfigurationFile", () => { const EMPTY_CONFIG: IConfigurationFile = { jsRules: {}, + linterOptions: {}, rules: {}, rulesDirectory: [], }; @@ -33,72 +33,80 @@ describe("Configuration", () => { assert.deepEqual(extendConfigurationFile(EMPTY_CONFIG, {}), EMPTY_CONFIG); assert.deepEqual(extendConfigurationFile({}, { jsRules: { row: "oar" }, + linterOptions: {}, rules: { foo: "bar" }, rulesDirectory: "foo", }), { jsRules: { row: "oar" }, + linterOptions: {}, rules: {foo: "bar"}, rulesDirectory: ["foo"], }); - assert.deepEqual(extendConfigurationFile({ + const actualConfig = extendConfigurationFile({ jsRules: { row: "oar" }, + linterOptions: {}, rules: { a: 1, - b: 2, + b: 1, }, rulesDirectory: ["foo", "bar"], }, { jsRules: { fly: "wings" }, + linterOptions: {}, rules: { - b: 1, + b: 2, c: 3, }, rulesDirectory: "baz", - }), { + }); + /* tslint:disable:object-literal-sort-keys */ + const expectedConfig = { jsRules: { - fly: "wings", row: "oar", + fly: "wings", }, + linterOptions: {}, rules: { a: 1, b: 2, c: 3, }, rulesDirectory: ["foo", "bar", "baz"], - }); + }; + assert.deepEqual(actualConfig, expectedConfig); }); describe("loadConfigurationFromPath", () => { it("extends with relative path", () => { - let config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json"); + const config = loadConfigurationFromPath("./test/config/tslint-extends-relative.json"); assert.isArray(config.rulesDirectory); - assert.isTrue(config.rules["no-fail"]); - assert.isFalse(config.rules["always-fail"]); + assert.isTrue(config.rules["no-fail"], "did not pick up 'no-fail' in base config"); + assert.isFalse(config.rules["always-fail"], "did not set 'always-fail' in top config"); assert.isTrue(config.jsRules["no-fail"]); assert.isFalse(config.jsRules["always-fail"]); }); it("extends with package", () => { - let config = loadConfigurationFromPath("./test/config/tslint-extends-package.json"); + const config = loadConfigurationFromPath("./test/config/tslint-extends-package.json"); assert.isArray(config.rulesDirectory); /* tslint:disable:object-literal-sort-keys */ assert.deepEqual(config.jsRules, { "rule-one": true, - "rule-two": true, "rule-three": false, + "rule-two": true, }); assert.deepEqual(config.rules, { "rule-one": true, - "rule-two": true, "rule-three": false, + "rule-two": true, }); /* tslint:enable:object-literal-sort-keys */ }); it("extends with package without customization", () => { - let config = loadConfigurationFromPath("./test/config/tslint-extends-package-no-mod.json"); + const config = loadConfigurationFromPath("./test/config/tslint-extends-package-no-mod.json"); assert.isArray(config.rulesDirectory); assert.deepEqual(config.jsRules, { @@ -113,7 +121,7 @@ describe("Configuration", () => { it("extends with builtin", () => { const config = loadConfigurationFromPath("./test/config/tslint-extends-builtin.json"); - assert.isTrue(config.jsRules["no-var-keyword"]); + assert.isUndefined(config.jsRules["no-var-keyword"]); assert.isFalse(config.jsRules["no-eval"]); assert.isTrue(config.rules["no-var-keyword"]); assert.isFalse(config.rules["no-eval"]); @@ -123,16 +131,7 @@ describe("Configuration", () => { let tmpfile: string; beforeEach(() => { - for (let i = 0; i < 5; i++) { - const attempt = path.join(os.tmpdir(), `tslint.test${Math.round(Date.now() * Math.random())}.json`); - if (!fs.existsSync(tmpfile)) { - tmpfile = attempt; - break; - } - } - if (tmpfile === undefined) { - throw new Error("Couldn't create temp file"); - } + tmpfile = createTempFile("json"); }); afterEach(() => { @@ -144,9 +143,6 @@ describe("Configuration", () => { it("extends with package installed relative to tslint", () => { fs.writeFileSync(tmpfile, JSON.stringify({ extends: "tslint-test-config-non-relative" })); let config = loadConfigurationFromPath(tmpfile); - assert.deepEqual(config.jsRules, { - "class-name": true, - }); assert.deepEqual(config.rules, { "class-name": true, }); @@ -184,13 +180,13 @@ describe("Configuration", () => { "always-fail": false, "no-fail": true, "rule-one": true, - "rule-two": false, + "rule-two": true, }); assert.deepEqual(config.rules, { "always-fail": false, "no-fail": true, "rule-one": true, - "rule-two": false, + "rule-two": true, }); }); diff --git a/test/executable/executableTests.ts b/test/executable/executableTests.ts index 61e24ce8ff8..08655c96b56 100644 --- a/test/executable/executableTests.ts +++ b/test/executable/executableTests.ts @@ -14,6 +14,7 @@ * 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"; @@ -24,8 +25,9 @@ const EXECUTABLE_DIR = path.resolve(process.cwd(), "test", "executable"); const EXECUTABLE_PATH = path.resolve(EXECUTABLE_DIR, "npm-like-executable"); const TEMP_JSON_PATH = path.resolve(EXECUTABLE_DIR, "tslint.json"); -describe("Executable", () => { - +/* tslint:disable:only-arrow-functions */ +describe("Executable", function() { + this.slow(3000); // the executable is JIT-ed each time it runs; avoid showing slowness warnings describe("Files", () => { it("exits with code 1 if no arguments passed", (done) => { execCli([], (err, stdout, stderr) => { @@ -66,23 +68,33 @@ describe("Executable", () => { describe("Configuration file", () => { it("exits with code 0 if relative path is passed without `./`", (done) => { - execCli(["-c", "test/config/tslint-almost-empty.json", "src/tslint.ts"], (err) => { + execCli(["-c", "test/config/tslint-almost-empty.json", "src/test.ts"], (err) => { assert.isNull(err, "process should exit without an error"); done(); }); }); it("exits with code 0 if config file that extends relative config file", (done) => { - execCli(["-c", "test/config/tslint-extends-package-no-mod.json", "src/tslint.ts"], (err) => { + execCli(["-c", "test/config/tslint-extends-package-no-mod.json", "src/test.ts"], (err) => { assert.isNull(err, "process should exit without an error"); done(); + }); }); + + it("exits with code 1 if config file is invalid", (done) => { + execCli(["-c", "test/config/tslint-invalid.json", "src/test.ts"], (err, stdout, stderr) => { + assert.isNotNull(err, "process should exit with error"); + assert.strictEqual(err.code, 1, "error code should be 1"); + + assert.include(stderr, "Failed to load", "stderr should contain notification about failing to load json"); + assert.strictEqual(stdout, "", "shouldn't contain any output in stdout"); + done(); }); }); }); describe("Custom rules", () => { it("exits with code 1 if nonexisting custom rules directory is passed", (done) => { - execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./someRandomDir", "src/tslint.ts"], (err) => { + execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./someRandomDir", "src/test.ts"], (err) => { assert.isNotNull(err, "process should exit with error"); assert.strictEqual(err.code, 1, "error code should be 1"); done(); @@ -90,7 +102,7 @@ describe("Executable", () => { }); it("exits with code 2 if custom rules directory is passed and file contains lint errors", (done) => { - execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "src/tslint.ts"], (err) => { + execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "src/test.ts"], (err) => { assert.isNotNull(err, "process should exit with error"); assert.strictEqual(err.code, 2, "error code should be 2"); done(); @@ -98,25 +110,35 @@ describe("Executable", () => { }); it("exits with code 2 if custom rules directory is specified in config file and file contains lint errors", (done) => { - execCli(["-c", "./test/config/tslint-custom-rules-with-dir.json", "src/tslint.ts"], (err) => { + execCli(["-c", "./test/config/tslint-custom-rules-with-dir.json", "src/test.ts"], (err) => { assert.isNotNull(err, "process should exit with error"); assert.strictEqual(err.code, 2, "error code should be 2"); done(); }); }); + }); - it("exits with code 2 if several custom rules directories are specified in config file and file contains lint errors", (done) => { - execCli(["-c", "./test/config/tslint-custom-rules-with-two-dirs.json", "src/tslint.ts"], (err) => { - assert.isNotNull(err, "process should exit with error"); - assert.strictEqual(err.code, 2, "error code should be 2"); - done(); - }); + describe("--fix flag", () => { + it("fixes multiple rules without overwriting each other", (done) => { + const tempFile = createTempFile("ts"); + fs.createReadStream("test/files/multiple-fixes-test/multiple-fixes.test.ts").pipe(fs.createWriteStream(tempFile)); + execCli(["-c", "test/files/multiple-fixes-test/tslint.json", tempFile, "--fix"], + (err, stdout) => { + const content = fs.readFileSync(tempFile, "utf8"); + // compare against file name which will be returned by formatter (used in TypeScript) + const denormalizedFileName = denormalizeWinPath(tempFile); + fs.unlinkSync(tempFile); + assert.strictEqual(content, "import * as y from \"a_long_module\";\nimport * as x from \"b\";\n"); + assert.isNull(err, "process should exit without an error"); + assert.strictEqual(stdout, `Fixed 2 error(s) in ${denormalizedFileName}`); + done(); + }); }); }); describe("--force flag", () => { it("exits with code 0 if `--force` flag is passed", (done) => { - execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "--force", "src/tslint.ts"], + execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "--force", "src/test.ts"], (err, stdout) => { assert.isNull(err, "process should exit without an error"); assert.include(stdout, "failure", "errors should be reported"); @@ -216,6 +238,35 @@ describe("Executable", () => { }); }); + + describe("globs and quotes", () => { + // when glob pattern is passed without quotes in npm script `process.env` will contain: + // on Windows - pattern string without any quotes + // on Linux - list of files that matches glob (may differ from `glob` module results) + + it("exits with code 2 if correctly finds file containing lint errors when glob is in double quotes", (done) => { + // when glob pattern is passed in double quotes in npm script `process.env` will contain: + // on Windows - pattern string without any quotes + // on Linux - pattern string without any quotes (glob is not expanded) + execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "src/**/test.ts"], (err) => { + assert.isNotNull(err, "process should exit with error"); + assert.strictEqual(err.code, 2, "error code should be 2"); + done(); + }); + }); + + it("exits with code 2 if correctly finds file containing lint errors when glob is in single quotes", (done) => { + // when glob pattern is passed in single quotes in npm script `process.env` will contain: + // on Windows - pattern string wrapped in single quotes + // on Linux - pattern string without any quotes (glob is not expanded) + execCli(["-c", "./test/config/tslint-custom-rules.json", "-r", "./test/files/custom-rules", "'src/**/test.ts'"], (err) => { + assert.isNotNull(err, "process should exit with error"); + assert.strictEqual(err.code, 2, "error code should be 2"); + done(); + }); + }); + + }); }); type ExecFileCallback = (error: any, stdout: string, stderr: string) => void; diff --git a/test/external/tslint-test-config-non-relative/tslint.json b/test/external/tslint-test-config-non-relative/tslint.json index fcaed86a61e..5078a1f987d 100644 --- a/test/external/tslint-test-config-non-relative/tslint.json +++ b/test/external/tslint-test-config-non-relative/tslint.json @@ -1,4 +1,7 @@ { + "jsRules": { + "class-name": true + }, "rules": { "class-name": true } diff --git a/test/external/tslint-test-config/tslint.json b/test/external/tslint-test-config/tslint.json index 2cf73c5a02d..2e3cdcef21a 100644 --- a/test/external/tslint-test-config/tslint.json +++ b/test/external/tslint-test-config/tslint.json @@ -1,5 +1,9 @@ { "extends": "tslint-test-custom-rules", + "jsRules": { + "rule-two": true, + "rule-four": true + }, "rules": { "rule-two": true, "rule-four": true diff --git a/test/external/tslint-test-custom-rules/tslint.json b/test/external/tslint-test-custom-rules/tslint.json index 0f4be5d3c42..16ddc786809 100644 --- a/test/external/tslint-test-custom-rules/tslint.json +++ b/test/external/tslint-test-custom-rules/tslint.json @@ -1,5 +1,9 @@ { "rulesDirectory": ["./rules"], + "jsRules": { + "rule-one": true, + "rule-two": false + }, "rules": { "rule-one": true, "rule-two": false diff --git a/test/files/multiple-fixes-test/multiple-fixes.test.ts b/test/files/multiple-fixes-test/multiple-fixes.test.ts new file mode 100644 index 00000000000..83849f1a699 --- /dev/null +++ b/test/files/multiple-fixes-test/multiple-fixes.test.ts @@ -0,0 +1,2 @@ +import * as x from "b" +import * as y from "a_long_module"; diff --git a/test/files/multiple-fixes-test/tslint.json b/test/files/multiple-fixes-test/tslint.json new file mode 100644 index 00000000000..d57be419e19 --- /dev/null +++ b/test/files/multiple-fixes-test/tslint.json @@ -0,0 +1,6 @@ +{ + "rules": { + "ordered-imports": [true], + "semicolon": [true, "always"] + } +} diff --git a/test/formatters/proseFormatterTests.ts b/test/formatters/proseFormatterTests.ts index ccf8bc18907..0643e58bfdf 100644 --- a/test/formatters/proseFormatterTests.ts +++ b/test/formatters/proseFormatterTests.ts @@ -47,6 +47,28 @@ describe("Prose Formatter", () => { assert.equal(actualResult, expectedResult); }); + it("formats fixes", () => { + const failures = [ + new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), + ]; + + const mockFix = { getFileName: () => { return "file2"; } } as any; + + const fixes = [ + new RuleFailure(sourceFile, 0, 1, "first failure", "first-name"), + new RuleFailure(sourceFile, 32, 36, "mid failure", "mid-name"), + mockFix, + ]; + + const expectedResult = + `Fixed 2 error(s) in ${TEST_FILE}\n` + + `Fixed 1 error(s) in file2\n\n` + + `${TEST_FILE}${getPositionString(1, 1)}first failure\n`; + + const actualResult = formatter.format(failures, fixes); + assert.equal(actualResult, expectedResult); + }); + it("handles no failures", () => { const result = formatter.format([]); assert.equal(result, ""); diff --git a/test/formatters/stylishFormatterTests.ts b/test/formatters/stylishFormatterTests.ts index efa515fc929..a694ccf3e25 100644 --- a/test/formatters/stylishFormatterTests.ts +++ b/test/formatters/stylishFormatterTests.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import * as colors from "colors"; + import * as ts from "typescript"; import {IFormatter, RuleFailure, TestUtils} from "../lint"; @@ -43,12 +45,12 @@ describe("Stylish Formatter", () => { const maxPositionTuple = `${maxPositionObj.line + 1}:${maxPositionObj.character + 1}`; - const expectedResult = (require("colors").supportsColor) ? + const expectedResult = colors.enabled ? "formatters/stylishFormatter.test.ts" + "\n" + - "\u001b[31m1:1\u001b[39m \u001b[33mfirst-name\u001b[39m first failure" + "\n" + - "\u001b[31m1:3\u001b[39m \u001b[33mescape \u001b[39m &<>'\" should be escaped" + "\n" + - `\u001b[31m${maxPositionTuple}\u001b[39m \u001b[33mlast-name \u001b[39m last failure` + "\n" + - "\u001b[31m1:1\u001b[39m \u001b[33mfull-name \u001b[39m full failure" + "\n" + + "\u001b[31m1:1\u001b[39m \u001b[90mfirst-name\u001b[39m \u001b[33mfirst failure\u001b[39m" + "\n" + + "\u001b[31m1:3\u001b[39m \u001b[90mescape \u001b[39m \u001b[33m&<>'\" should be escaped\u001b[39m" + "\n" + + `\u001b[31m${maxPositionTuple}\u001b[39m \u001b[90mlast-name \u001b[39m \u001b[33mlast failure\u001b[39m` + "\n" + + "\u001b[31m1:1\u001b[39m \u001b[90mfull-name \u001b[39m \u001b[33mfull failure\u001b[39m" + "\n" + "\n" : "formatters/stylishFormatter.test.ts" + "\n" + "1:1 first-name first failure" + "\n" + diff --git a/test/lint.ts b/test/lint.ts index baa9d0fd203..b3336b4473d 100644 --- a/test/lint.ts +++ b/test/lint.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -export * from "../src/lint"; +export * from "../src/index"; import * as TestUtils from "./utils"; diff --git a/test/ruleLoaderTests.ts b/test/ruleLoaderTests.ts index 70324afd3bb..fc75dc77e27 100644 --- a/test/ruleLoaderTests.ts +++ b/test/ruleLoaderTests.ts @@ -1,4 +1,5 @@ -/* +/** + * @license * Copyright 2013 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +41,7 @@ describe("Rule Loader", () => { assert.throws( () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY), - /invalidConfig1\ninvalidConfig2/ + /invalidConfig1\ninvalidConfig2/, ); }); @@ -56,7 +57,7 @@ describe("Rule Loader", () => { assert.throws( () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY), - /_indent\nforin_\n-quotemark\neofline-/ + /_indent\nforin_\n-quotemark\neofline-/, ); }); @@ -89,7 +90,7 @@ describe("Rule Loader", () => { assert.throws( () => loadRules(invalidConfiguration, {}, RULES_DIRECTORY, true), - /array-type/ + /array-type/, ); }); }); diff --git a/test/rules/_integration/react/tslint.json b/test/rules/_integration/react/tslint.json index 4cc8c887f8e..9af967c5098 100644 --- a/test/rules/_integration/react/tslint.json +++ b/test/rules/_integration/react/tslint.json @@ -3,14 +3,13 @@ "curly": true, "eofline": true, "indent": [true, "spaces"], - "max-line-length": true, + "max-line-length": [true, 120], "no-bitwise": true, - "no-unreachable": true, "no-unused-expression": true, "no-unused-variable": true, "no-use-before-declare": true, "quotemark": [true, "double"], - "semicolon": true, + "semicolon": [true, "always"], "whitespace": [true, "check-branch", "check-decl", diff --git a/test/rules/adjacent-overload-signatures/test.ts.lint b/test/rules/adjacent-overload-signatures/test.ts.lint index c7e10afcb2e..9b65ef3d1b2 100644 --- a/test/rules/adjacent-overload-signatures/test.ts.lint +++ b/test/rules/adjacent-overload-signatures/test.ts.lint @@ -83,3 +83,31 @@ interface b5 { ~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] } } + +// Also works in classes, source files, modules, namespaces + +class C { + a(): void; + b(): void; + a(x: number): void; + ~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] +} + +declare function a(): void; +declare function b(): void; +declare function a(x: number): void; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] + +declare module "m" { + export function a(): void; + export function b(): void; + export function a(x: number): void; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] +} + +declare namespace N { + export function a(): void; + export function b(): void; + export function a(x: number): void; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [All 'a' signatures should be adjacent] +} diff --git a/test/rules/arrow-parens/test.ts.fix b/test/rules/arrow-parens/test.ts.fix new file mode 100644 index 00000000000..780c4f61339 --- /dev/null +++ b/test/rules/arrow-parens/test.ts.fix @@ -0,0 +1,27 @@ +// valid case +var a = (a) => {}; +var b = (a: number) => {}; +var c = (a, b) => {}; +var f = (...rest) => {}; +var f = a: number => {}; // TSLint don't warn. But syntax is wrong. +class Foo { + a: (a) =>{} +} +var bar = (method: () => T) => { + method(); +}; +var barbar = (method: (a: any) => T) => { + method(""); +}; +var barbarbar = (method: (a) => T) => { + method(""); +}; +var piyo = (method: () => T) => { + method(); +}; +const validAsync = async (param: any) => {}; +const validAsync = async (param) => {}; + +// invalid case +var e = ((a) => {})(1); +var f = (ab) => {}; diff --git a/test/rules/arrow-parens/test.ts.lint b/test/rules/arrow-parens/test.ts.lint index 2975baae8aa..463decd2270 100644 --- a/test/rules/arrow-parens/test.ts.lint +++ b/test/rules/arrow-parens/test.ts.lint @@ -25,5 +25,5 @@ const validAsync = async (param) => {}; // invalid case var e = (a => {})(1); ~ [Parentheses are required around the parameters of an arrow function definition] -var f = a => {}; - ~ [Parentheses are required around the parameters of an arrow function definition] +var f = ab => {}; + ~~ [Parentheses are required around the parameters of an arrow function definition] diff --git a/test/rules/completed-docs/all/test.ts.lint b/test/rules/completed-docs/all/test.ts.lint new file mode 100644 index 00000000000..0cdc5889ed6 --- /dev/null +++ b/test/rules/completed-docs/all/test.ts.lint @@ -0,0 +1,59 @@ +class BadClass { +~~~~~~~~~~~~~~~~ [0] + badProperty; + ~~~~~~~~~~~~ [1] + badMethod() { } + ~~~~~~~~~~~~~~~ [2] +} + +/** + * + */ +class EmptyClass { +~~~~~~~~~~~~~~~~~~ [0] + /** + * + */ + emptyProperty; + ~~~~~~~~~~~~~~ [1] + + /** + * + */ + emptyMethod() { } + ~~~~~~~~~~~~~~~~~ [2] +} + +/** + * ... + */ +class GoodClass { + /** + * ... + */ + goodProperty; + + /** + * ... + */ + goodMethod() { } +} + +function BadFunction() { } +~~~~~~~~~~~~~~~~~~~~~~~~~~ [3] + +/** + * + */ +function EmptyFunction() { } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [3] + +/** + * ... + */ +function GoodFunction() { } + +[0]: Classes must have documentation. +[1]: Properties must have documentation. +[2]: Methods must have documentation. +[3]: Functions must have documentation. diff --git a/test/rules/completed-docs/all/tslint.json b/test/rules/completed-docs/all/tslint.json new file mode 100644 index 00000000000..8dce9903cd7 --- /dev/null +++ b/test/rules/completed-docs/all/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "completed-docs": true + } +} diff --git a/test/rules/completed-docs/functions/test.ts.lint b/test/rules/completed-docs/functions/test.ts.lint new file mode 100644 index 00000000000..4fbe4304713 --- /dev/null +++ b/test/rules/completed-docs/functions/test.ts.lint @@ -0,0 +1,50 @@ +class BadClass { + badMember; + badFunction() { } +} + +/** + * + */ +class EmptyClass { + /** + * + */ + emptyProperty; + + /** + * + */ + emptyMethod() { } +} + +/** + * ... + */ +class GoodClass { + /** + * ... + */ + goodProperty; + + /** + * ... + */ + goodMethod() { } +} + +function BadFunction() { } +~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +/** + * + */ +function EmptyFunction() { } +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [0] + +/** + * ... + */ +function GoodFunction() { } + +[0]: Functions must have documentation. diff --git a/test/rules/completed-docs/functions/tslint.json b/test/rules/completed-docs/functions/tslint.json new file mode 100644 index 00000000000..47a6dcf43f6 --- /dev/null +++ b/test/rules/completed-docs/functions/tslint.json @@ -0,0 +1,8 @@ +{ + "linterOptions": { + "typeCheck": true + }, + "rules": { + "completed-docs": [true, "functions"] + } +} diff --git a/test/rules/indent/spaces/test.ts.lint b/test/rules/indent/spaces/test.ts.lint index 106b76ece18..0ad97bb449c 100644 --- a/test/rules/indent/spaces/test.ts.lint +++ b/test/rules/indent/spaces/test.ts.lint @@ -18,6 +18,16 @@ module TestModule { c: 3 }; + // ignore leading tabs inside template strings + var s1 = ` + multiline` + ` template + string`; + var s2 = ` + multiline ${ "A" } + template ${ "B" + + "C" } + string`; + export enum TestEnum { VALUE1, VALUE2 diff --git a/test/rules/indent/tabs/test.ts.lint b/test/rules/indent/tabs/test.ts.lint index 1ec440ff490..a8b16546010 100644 --- a/test/rules/indent/tabs/test.ts.lint +++ b/test/rules/indent/tabs/test.ts.lint @@ -21,6 +21,16 @@ module TestModule { c: 3 }; + // ignore leading spaces inside template strings + var s1 = ` + multiline` + ` template + string`; + var s2 = ` + multiline ${ "A" } + template ${ "B" + + "C" } + string`; + export enum TestEnum { VALUE1, VALUE2 diff --git a/test/rules/no-unreachable/test.js.lint b/test/rules/no-unreachable/test.js.lint deleted file mode 100644 index 8d6e59e64b4..00000000000 --- a/test/rules/no-unreachable/test.js.lint +++ /dev/null @@ -1,104 +0,0 @@ -// invalid code - -function f1() { - var x = 3; - return; - var y; - ~~~~~~ [unreachable code] - var z; -} - -var f2 = () => { - if (x === 3) { - throw new Error("error"); - "123"; - ~~~~~~ [unreachable code] - } else { - return y; - } - - return 123; -}; - -lab: -for (var i = 0; i < 10; ++i) { - if (i === 3) { - break; - console.log("hi"); - ~~~~~~~~~~~~~~~~~~ [unreachable code] - } else { - continue lab; - i = 4; - ~~~~~~ [unreachable code] - } -} - -// valid code -var f2 = () => { - if (x === 3) { - throw new Error("error"); - } else { - return y; - } - - return 123; -}; - -switch (x) { - case 1: - i = 2; - break; - case 2: - i = 3; - break; - default: - i = 4; - break; -} - -function f4() { - var x = 3; - if (x === 4) return; - else x = 4; - var y = 7; -} - -function f5() { - var x = 3; - if (x === 4) x = 5; - else return; - var y = 7; -} - -function f6() { - hoisted(); - return 0; - - function hoisted() { - return 0; - } -} - -// more invalid code - -function f7() { - hoisted(); - return 0; - - function hoisted() { - return 0; - } - - var y = 7; - ~~~~~~~~~~ [unreachable code] -} - -// more valid code - -function f8() { - try { - return 0; - } catch (e) { - console.log("here"); - } -} diff --git a/test/rules/no-unreachable/test.ts.lint b/test/rules/no-unreachable/test.ts.lint deleted file mode 100644 index 15d51b06714..00000000000 --- a/test/rules/no-unreachable/test.ts.lint +++ /dev/null @@ -1,118 +0,0 @@ -// invalid code - -function f1() { - var x = 3; - return; - var y; - ~~~~~~ [unreachable code] - var z; -} - -var f2 = () => { - if (x === 3) { - throw new Error("error"); - "123"; - ~~~~~~ [unreachable code] - } else { - return y; - } - - return 123; -}; - -lab: -for (var i = 0; i < 10; ++i) { - if (i === 3) { - break; - console.log("hi"); - ~~~~~~~~~~~~~~~~~~ [unreachable code] - } else { - continue lab; - i = 4; - ~~~~~~ [unreachable code] - } -} - -// valid code -var f2 = () => { - if (x === 3) { - throw new Error("error"); - } else { - return y; - } - - return 123; -}; - -switch (x) { - case 1: - i = 2; - break; - case 2: - i = 3; - break; - default: - i = 4; - break; -} - -function f4() { - var x = 3; - if (x === 4) return; - else x = 4; - var y = 7; -} - -function f5() { - var x = 3; - if (x === 4) x = 5; - else return; - var y = 7; -} - -function f6() { - hoisted(); - return 0; - - function hoisted() { - return 0; - } -} - -// more invalid code - -function f7() { - hoisted(); - return 0; - - function hoisted() { - return 0; - } - - var y = 7; - ~~~~~~~~~~ [unreachable code] -} - -// more valid code - -function f8() { - try { - return 0; - } catch (e) { - console.log("here"); - } -} - -// valid case - -function f9() { - return 1; - function bar() {} - type Bar = string; -} - -function f10() { - type Bar = string; - return 1; - function bar() {} -} \ No newline at end of file diff --git a/test/rules/no-unreachable/tslint.json b/test/rules/no-unreachable/tslint.json deleted file mode 100644 index 80eb8a090fb..00000000000 --- a/test/rules/no-unreachable/tslint.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rules": { - "no-unreachable": true - }, - "jsRules": { - "no-unreachable": true - } -} diff --git a/test/rules/object-literal-key-quotes/consistent-as-needed/test.js.lint b/test/rules/object-literal-key-quotes/consistent-as-needed/test.js.lint new file mode 100644 index 00000000000..c7ff0f38013 --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent-as-needed/test.js.lint @@ -0,0 +1,50 @@ + +const o = { + 'hello': 123, + ~~~~~~~ [Unnecessarily quoted property 'hello' found.] + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const v = { + "hello": 123, + ~~~~~~~ [Unnecessarily quoted property 'hello' found.] + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const s = { + hello: 123, + bye: 45, +}; +const r = { + "hello": 123, + "bye-bye": 45, +}; +const p = { + hello: 123, + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const q = { + hello: 123, + ~~~~~ [Unquoted property 'hello' found.] + "bye-bye": 45, +}; +const t = { + hello: 123, + bye-bye: 45, + nested: { + "bird": 2, + ~~~~~~ [Unnecessarily quoted property 'bird' found.] + egg: 3, + } +}; +const u = { + hello: 123, + bye: 45, + nested: { + "bird": 1, + ~~~~~~ [Unnecessarily quoted property 'bird' found.] + "egg": 2, + ~~~~~ [Unnecessarily quoted property 'egg' found.] + } +}; \ No newline at end of file diff --git a/test/rules/object-literal-key-quotes/consistent-as-needed/test.ts.lint b/test/rules/object-literal-key-quotes/consistent-as-needed/test.ts.lint new file mode 100644 index 00000000000..c7ff0f38013 --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent-as-needed/test.ts.lint @@ -0,0 +1,50 @@ + +const o = { + 'hello': 123, + ~~~~~~~ [Unnecessarily quoted property 'hello' found.] + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const v = { + "hello": 123, + ~~~~~~~ [Unnecessarily quoted property 'hello' found.] + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const s = { + hello: 123, + bye: 45, +}; +const r = { + "hello": 123, + "bye-bye": 45, +}; +const p = { + hello: 123, + "bye": 45, + ~~~~~ [Unnecessarily quoted property 'bye' found.] +}; +const q = { + hello: 123, + ~~~~~ [Unquoted property 'hello' found.] + "bye-bye": 45, +}; +const t = { + hello: 123, + bye-bye: 45, + nested: { + "bird": 2, + ~~~~~~ [Unnecessarily quoted property 'bird' found.] + egg: 3, + } +}; +const u = { + hello: 123, + bye: 45, + nested: { + "bird": 1, + ~~~~~~ [Unnecessarily quoted property 'bird' found.] + "egg": 2, + ~~~~~ [Unnecessarily quoted property 'egg' found.] + } +}; \ No newline at end of file diff --git a/test/rules/object-literal-key-quotes/consistent-as-needed/tslint.json b/test/rules/object-literal-key-quotes/consistent-as-needed/tslint.json new file mode 100644 index 00000000000..f597be075cc --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent-as-needed/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "object-literal-key-quotes": [true, "consistent-as-needed"] + }, + "jsRules": { + "object-literal-key-quotes": [true, "consistent-as-needed"] + } +} diff --git a/test/rules/object-literal-key-quotes/consistent/test.js.lint b/test/rules/object-literal-key-quotes/consistent/test.js.lint new file mode 100644 index 00000000000..a2ee53079b5 --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent/test.js.lint @@ -0,0 +1,44 @@ + +const o = { + 'hello': 123, + "bye": 45, +}; +const v = { + "hello": 123, + "bye": 45, +}; +const s = { + hello: 123, + bye: 45, +}; +const r = { + "hello": 123, + "bye-bye": 45, +}; +const p = { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + hello: 123, + "bye": 45, +}; +const q = { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + hello: 123, + "bye-bye": 45, +}; +const t = { + hello: 123, + bye-bye: 45, + nested: { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + "bird": 2, + egg: 3, + } +}; +const u = { + hello: 123, + bye: 45, + nested: { + "bird": 1, + "egg": 2, + } +}; \ No newline at end of file diff --git a/test/rules/object-literal-key-quotes/consistent/test.ts.lint b/test/rules/object-literal-key-quotes/consistent/test.ts.lint new file mode 100644 index 00000000000..a2ee53079b5 --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent/test.ts.lint @@ -0,0 +1,44 @@ + +const o = { + 'hello': 123, + "bye": 45, +}; +const v = { + "hello": 123, + "bye": 45, +}; +const s = { + hello: 123, + bye: 45, +}; +const r = { + "hello": 123, + "bye-bye": 45, +}; +const p = { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + hello: 123, + "bye": 45, +}; +const q = { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + hello: 123, + "bye-bye": 45, +}; +const t = { + hello: 123, + bye-bye: 45, + nested: { + ~ [All property names in this object literal must be consistently quoted or unquoted.] + "bird": 2, + egg: 3, + } +}; +const u = { + hello: 123, + bye: 45, + nested: { + "bird": 1, + "egg": 2, + } +}; \ No newline at end of file diff --git a/test/rules/object-literal-key-quotes/consistent/tslint.json b/test/rules/object-literal-key-quotes/consistent/tslint.json new file mode 100644 index 00000000000..5c9b583e66b --- /dev/null +++ b/test/rules/object-literal-key-quotes/consistent/tslint.json @@ -0,0 +1,8 @@ +{ + "rules": { + "object-literal-key-quotes": [true, "consistent"] + }, + "jsRules": { + "object-literal-key-quotes": [true, "consistent"] + } +} diff --git a/test/rules/ordered-imports/case-insensitive/test.js.lint b/test/rules/ordered-imports/case-insensitive/test.js.lint index bd7ee497234..2efabb17c31 100644 --- a/test/rules/ordered-imports/case-insensitive/test.js.lint +++ b/test/rules/ordered-imports/case-insensitive/test.js.lint @@ -25,7 +25,8 @@ import {A, B} from 'Foo'; // should not fail // Other styles of import statements. import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; -import someDefault from "module"; +import someDefault from "module"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] import "something"; [ordered-imports]: Named imports must be alphabetized. diff --git a/test/rules/ordered-imports/case-insensitive/test.ts.fix b/test/rules/ordered-imports/case-insensitive/test.ts.fix new file mode 100644 index 00000000000..fe75ec2d0f2 --- /dev/null +++ b/test/rules/ordered-imports/case-insensitive/test.ts.fix @@ -0,0 +1,44 @@ +// Named imports should be alphabetized. +import {A, B} from 'foo'; +import {A, B} from 'foo'; // failure + +// Case is irrelevant for named import ordering. +import {A, bz, C} from 'foo'; // failure +import {A, b, C} from 'zfoo'; + +import {g} from "y"; // failure +import { + a as d, + b as c, +} from "z"; + +// Import sources should be alphabetized. +import * as bar from 'bar'; +import * as foo from 'foo'; + +import * as abc from 'abc'; +import * as bar from 'bar'; // failure +import * as foo from 'foo'; + +// ignore quotes +import * as bar from 'bar'; +import * as foo from "foo"; + +import * as bar from "bar"; +import * as foo from 'foo'; + +// Case is irrelevant for source import ordering. +import {A, B} from 'Bar'; +import {A, B} from 'baz'; +import {A, B} from 'Foo'; // should not fail + +// Other styles of import statements. +import someDefault from "module"; +import "something"; +import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; + +// do not fix cases where a newline is missing +import * as foo from 'foo'; import * as bar from 'bar'; + +import * as foo from 'foo'; +import * as bar from 'bar'; \ No newline at end of file diff --git a/test/rules/ordered-imports/case-insensitive/test.ts.lint b/test/rules/ordered-imports/case-insensitive/test.ts.lint index bd7ee497234..441d926d90d 100644 --- a/test/rules/ordered-imports/case-insensitive/test.ts.lint +++ b/test/rules/ordered-imports/case-insensitive/test.ts.lint @@ -4,10 +4,19 @@ import {B, A} from 'foo'; // failure ~~~~ [ordered-imports] // Case is irrelevant for named import ordering. -import {A, b, C} from 'foo'; -import {b, A, C} from 'foo'; // failure - ~~~~ [ordered-imports] +import {A, b, C} from 'zfoo'; +import {bz, A, C} from 'foo'; // failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] + ~~~~~ [Named imports must be alphabetized.] +import { + b as c, + ~~~~~~~ + a as d, +~~~~~~~~~~ [Named imports must be alphabetized.] +} from "z"; +import {g} from "y"; // failure +~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] // Import sources should be alphabetized. import * as bar from 'bar'; @@ -18,6 +27,13 @@ import * as foo from 'foo'; import * as bar from 'bar'; // failure ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ordered-sources] +// ignore quotes +import * as bar from 'bar'; +import * as foo from "foo"; + +import * as bar from "bar"; +import * as foo from 'foo'; + // Case is irrelevant for source import ordering. import {A, B} from 'Bar'; import {A, B} from 'baz'; @@ -26,7 +42,15 @@ import {A, B} from 'Foo'; // should not fail // Other styles of import statements. import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; import someDefault from "module"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] import "something"; +// do not fix cases where a newline is missing +import * as foo from 'foo'; import * as bar from 'bar'; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] + +import * as foo from 'foo'; +import * as bar from 'bar'; +~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] [ordered-imports]: Named imports must be alphabetized. -[ordered-sources]: Import sources within a group must be alphabetized. +[ordered-sources]: Import sources within a group must be alphabetized. \ No newline at end of file diff --git a/test/rules/ordered-imports/import-sources-any/test.ts.fix b/test/rules/ordered-imports/import-sources-any/test.ts.fix new file mode 100644 index 00000000000..a523be9f420 --- /dev/null +++ b/test/rules/ordered-imports/import-sources-any/test.ts.fix @@ -0,0 +1,26 @@ +// Named imports should be alphabetized. +import {A, B} from 'foo'; +import {A, B} from 'foo'; // failure + +// Case is irrelevant for named import ordering. +import {A, b, C} from 'zfoo'; +import {A, bz, C} from 'foo'; // failure + +// Import sources don't need to be alphabetized. +import * as bar from 'bar'; +import * as foo from 'foo'; + +import * as abc from 'abc'; +import * as foo from 'foo'; +import * as bar from 'bar'; // should not fail + +// Case is irrelevant for source import ordering. +import {A, B} from 'Bar'; +import {A, B} from 'baz'; +import {A, B} from 'Foo'; // should not fail + +// Other styles of import statements. +import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; +import someDefault from "module"; +import "something"; + diff --git a/test/rules/ordered-imports/import-sources-any/test.ts.lint b/test/rules/ordered-imports/import-sources-any/test.ts.lint index ab21388c54c..2e6ab32acad 100644 --- a/test/rules/ordered-imports/import-sources-any/test.ts.lint +++ b/test/rules/ordered-imports/import-sources-any/test.ts.lint @@ -4,10 +4,9 @@ import {B, A} from 'foo'; // failure ~~~~ [ordered-imports] // Case is irrelevant for named import ordering. -import {A, b, C} from 'foo'; -import {b, A, C} from 'foo'; // failure - ~~~~ [ordered-imports] - +import {A, b, C} from 'zfoo'; +import {bz, A, C} from 'foo'; // failure + ~~~~~ [ordered-imports] // Import sources don't need to be alphabetized. import * as bar from 'bar'; diff --git a/test/rules/ordered-imports/lowercase-first/test.js.lint b/test/rules/ordered-imports/lowercase-first/test.js.lint index ef8de7ea134..eff6e21b639 100644 --- a/test/rules/ordered-imports/lowercase-first/test.js.lint +++ b/test/rules/ordered-imports/lowercase-first/test.js.lint @@ -25,7 +25,8 @@ import {A, B} from 'Foo'; // Other styles of import statements. import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; -import someDefault from "module"; +import someDefault from "module"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] import "something"; [ordered-imports]: Named imports must be alphabetized. diff --git a/test/rules/ordered-imports/lowercase-first/test.ts.fix b/test/rules/ordered-imports/lowercase-first/test.ts.fix new file mode 100644 index 00000000000..324ec13d968 --- /dev/null +++ b/test/rules/ordered-imports/lowercase-first/test.ts.fix @@ -0,0 +1,26 @@ +// Named imports should be alphabetized. +import {A, B} from 'foo'; +import {A, B} from 'foo'; // failure + +// Lowercase comes before uppercase. +import {bz, A, C} from 'foo'; // failure +import {b, A, C} from 'zfoo'; + +// Import sources should be alphabetized. +import * as bar from 'bar'; +import * as foo from 'foo'; + +import * as abc from 'abc'; +import * as bar from 'bar'; // failure +import * as foo from 'foo'; + +// Lowercase comes before uppercase +import {A, B} from 'baz'; +import {A, B} from 'Bar'; +import {A, B} from 'Foo'; + +// Other styles of import statements. +import someDefault from "module"; +import "something"; +import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; + diff --git a/test/rules/ordered-imports/lowercase-first/test.ts.lint b/test/rules/ordered-imports/lowercase-first/test.ts.lint index ef8de7ea134..1ae184b3ba7 100644 --- a/test/rules/ordered-imports/lowercase-first/test.ts.lint +++ b/test/rules/ordered-imports/lowercase-first/test.ts.lint @@ -4,9 +4,10 @@ import {B, A} from 'foo'; // failure ~~~~ [ordered-imports] // Lowercase comes before uppercase. -import {b, A, C} from 'foo'; -import {A, b, C} from 'foo'; // failure - ~~~~ [ordered-imports] +import {A, b, C} from 'zfoo'; + ~~~~ [Named imports must be alphabetized.] +import {bz, A, C} from 'foo'; // failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] // Import sources should be alphabetized. import * as bar from 'bar'; @@ -26,6 +27,7 @@ import {A, B} from 'Foo'; // Other styles of import statements. import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; import someDefault from "module"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] import "something"; [ordered-imports]: Named imports must be alphabetized. diff --git a/test/rules/ordered-imports/named-imports-any/test.ts.fix b/test/rules/ordered-imports/named-imports-any/test.ts.fix new file mode 100644 index 00000000000..9e4e0e93811 --- /dev/null +++ b/test/rules/ordered-imports/named-imports-any/test.ts.fix @@ -0,0 +1,26 @@ +// Named imports do not need to be alphabetized. +import {A, B} from 'foo'; +import {B, A} from 'foo'; // should not fail + +// Case is irrelevant for named import ordering. +import {bz, A, C} from 'foo'; // should not fail +import {A, b, C} from 'zfoo'; + +// Import sources should be alphabetized. +import * as bar from 'bar'; +import * as foo from 'foo'; + +import * as abc from 'abc'; +import * as bar from 'bar'; // failure +import * as foo from 'foo'; + +// Case is irrelevant for source import ordering. +import {A, B} from 'Bar'; +import {A, B} from 'baz'; +import {A, B} from 'Foo'; // should not fail + +// Other styles of import statements. +import someDefault from "module"; +import "something"; +import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; + diff --git a/test/rules/ordered-imports/named-imports-any/test.ts.lint b/test/rules/ordered-imports/named-imports-any/test.ts.lint index 2e55118821b..e6707e9d62d 100644 --- a/test/rules/ordered-imports/named-imports-any/test.ts.lint +++ b/test/rules/ordered-imports/named-imports-any/test.ts.lint @@ -3,9 +3,9 @@ import {A, B} from 'foo'; import {B, A} from 'foo'; // should not fail // Case is irrelevant for named import ordering. -import {A, b, C} from 'foo'; -import {b, A, C} from 'foo'; // should not fail - +import {A, b, C} from 'zfoo'; +import {bz, A, C} from 'foo'; // should not fail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] // Import sources should be alphabetized. import * as bar from 'bar'; @@ -24,6 +24,7 @@ import {A, B} from 'Foo'; // should not fail // Other styles of import statements. import someDefault, {nameA, nameBReallyLong as anotherName} from "./wherever"; import someDefault from "module"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must be alphabetized.] import "something"; [ordered-imports]: Named imports must be alphabetized. diff --git a/test/rules/semicolon/always/test.js.lint b/test/rules/semicolon/always/test.js.lint index ee95f663803..31f9806a350 100644 --- a/test/rules/semicolon/always/test.js.lint +++ b/test/rules/semicolon/always/test.js.lint @@ -54,7 +54,19 @@ class MyClass { initializedProperty = 6 ~nil [Missing semicolon] - initializedMethodProperty = () => { return "hi"; } + public initializedMethodProperty = () => { + return "hi"; + }; + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~nil [Missing semicolon] } import {Router} from 'aurelia-router'; diff --git a/test/rules/semicolon/always/test.ts.fix b/test/rules/semicolon/always/test.ts.fix index 13fe1731386..049d84d358f 100644 --- a/test/rules/semicolon/always/test.ts.fix +++ b/test/rules/semicolon/always/test.ts.fix @@ -51,8 +51,17 @@ class MyClass { private email : string; public initializedProperty = 6; - public initializedMethodProperty = () => { return "hi"; } - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + } + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; }; } interface ITest { diff --git a/test/rules/semicolon/always/test.ts.lint b/test/rules/semicolon/always/test.ts.lint index be3ff2b5dc1..e99a82a7461 100644 --- a/test/rules/semicolon/always/test.ts.lint +++ b/test/rules/semicolon/always/test.ts.lint @@ -71,9 +71,19 @@ class MyClass { public initializedProperty = 6 ~nil [Missing semicolon] - public initializedMethodProperty = () => { return "hi"; }; - ~ [Unnecessary semicolon] - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + }; + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~nil [Missing semicolon] } interface ITest { diff --git a/test/rules/semicolon/enabled/test.ts.fix b/test/rules/semicolon/enabled/test.ts.fix index 2789b578e7e..ec4315f624d 100644 --- a/test/rules/semicolon/enabled/test.ts.fix +++ b/test/rules/semicolon/enabled/test.ts.fix @@ -4,6 +4,10 @@ a += b; c = () => { }; +f(() => { + return 1; +}); + d = function() { }; console.log("i am adam, am i?"); @@ -51,8 +55,17 @@ class MyClass { private email : string; public initializedProperty = 6; - public initializedMethodProperty = () => { return "hi"; } - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty: mytype = () => { + return "hi"; + } + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; }; } interface ITest { diff --git a/test/rules/semicolon/enabled/test.ts.lint b/test/rules/semicolon/enabled/test.ts.lint index 464f70bf905..928d3fac49b 100644 --- a/test/rules/semicolon/enabled/test.ts.lint +++ b/test/rules/semicolon/enabled/test.ts.lint @@ -7,6 +7,10 @@ c = () => { } ~nil [Missing semicolon] +f(() => { + return 1; +}); + d = function() { } ~nil [Missing semicolon] @@ -71,9 +75,19 @@ class MyClass { public initializedProperty = 6 ~nil [Missing semicolon] - public initializedMethodProperty = () => { return "hi"; }; - ~ [Unnecessary semicolon] - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty: mytype = () => { + return "hi"; + }; + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~nil [Missing semicolon] } interface ITest { diff --git a/test/rules/semicolon/ignore-bound-class-methods/test.ts.fix b/test/rules/semicolon/ignore-bound-class-methods/test.ts.fix index 6c7c6e551d6..3123c4670c0 100644 --- a/test/rules/semicolon/ignore-bound-class-methods/test.ts.fix +++ b/test/rules/semicolon/ignore-bound-class-methods/test.ts.fix @@ -51,8 +51,17 @@ class MyClass { private email : string; public initializedProperty = 6; - public initializedMethodProperty = () => { return "hi"; }; - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + }; + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; }; } interface ITest { diff --git a/test/rules/semicolon/ignore-bound-class-methods/test.ts.lint b/test/rules/semicolon/ignore-bound-class-methods/test.ts.lint index 574b2275734..f324082c60d 100644 --- a/test/rules/semicolon/ignore-bound-class-methods/test.ts.lint +++ b/test/rules/semicolon/ignore-bound-class-methods/test.ts.lint @@ -71,8 +71,18 @@ class MyClass { public initializedProperty = 6 ~nil [Missing semicolon] - public initializedMethodProperty = () => { return "hi"; }; - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + }; + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~nil [Missing semicolon] } interface ITest { diff --git a/test/rules/semicolon/ignore-interfaces/test.ts.fix b/test/rules/semicolon/ignore-interfaces/test.ts.fix index 6420c714d03..af3c4fe6bc6 100644 --- a/test/rules/semicolon/ignore-interfaces/test.ts.fix +++ b/test/rules/semicolon/ignore-interfaces/test.ts.fix @@ -51,8 +51,17 @@ class MyClass { private email : string; public initializedProperty = 6; - public initializedMethodProperty = () => { return "hi"; } - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + } + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; }; } interface ITest { diff --git a/test/rules/semicolon/ignore-interfaces/test.ts.lint b/test/rules/semicolon/ignore-interfaces/test.ts.lint index 4edd267c806..75259aed020 100644 --- a/test/rules/semicolon/ignore-interfaces/test.ts.lint +++ b/test/rules/semicolon/ignore-interfaces/test.ts.lint @@ -71,9 +71,19 @@ class MyClass { public initializedProperty = 6 ~nil [Missing semicolon] - public initializedMethodProperty = () => { return "hi"; }; - ~ [Unnecessary semicolon] - public initializedMethodPropertyWithoutSemicolon = () => { return "hi again"; } + public initializedMethodProperty = () => { + return "hi"; + }; + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~nil [Missing semicolon] } interface ITest { diff --git a/test/rules/semicolon/never/test.ts.fix b/test/rules/semicolon/never/test.ts.fix index a6e29364501..b089d624f0c 100644 --- a/test/rules/semicolon/never/test.ts.fix +++ b/test/rules/semicolon/never/test.ts.fix @@ -50,8 +50,17 @@ class MyClass { private index : number private email : string public initializedProperty = 6 - public initializedMethodProperty = () => { return "hi" } + public initializedMethodProperty = () => { + return "hi" + } + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again" + } + + public initializedMethodProperty1Line = () => { return "hi" } + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again" } } interface ITest { diff --git a/test/rules/semicolon/never/test.ts.lint b/test/rules/semicolon/never/test.ts.lint index bb4b4b02450..32fe4a65f6b 100644 --- a/test/rules/semicolon/never/test.ts.lint +++ b/test/rules/semicolon/never/test.ts.lint @@ -70,9 +70,23 @@ class MyClass { ~ [Unnecessary semicolon] private email : string public initializedProperty = 6 - public initializedMethodProperty = () => { return "hi" }; - ~ [Unnecessary semicolon] - + public initializedMethodProperty = () => { + return "hi"; + ~ [Unnecessary semicolon] + }; + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon = () => { + return "hi again"; + ~ [Unnecessary semicolon] + } + + public initializedMethodProperty1Line = () => { return "hi"; }; + ~ [Unnecessary semicolon] + ~ [Unnecessary semicolon] + + public initializedMethodPropertyWithoutSemicolon1Line = () => { return "hi again"; } + ~ [Unnecessary semicolon] } interface ITest { diff --git a/test/rules/trailing-comma/multiline-always/test.ts.fix b/test/rules/trailing-comma/multiline-always/test.ts.fix index cbee7ac25f7..ca1572a3f90 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.fix +++ b/test/rules/trailing-comma/multiline-always/test.ts.fix @@ -364,7 +364,7 @@ class Test { [ C, C, - ] + ], C, >, >( @@ -474,7 +474,7 @@ interface ITest { [ C, C, - ] + ], C, >, >( diff --git a/test/rules/trailing-comma/multiline-always/test.ts.lint b/test/rules/trailing-comma/multiline-always/test.ts.lint index 64f72d12a5f..3f62655e8ab 100644 --- a/test/rules/trailing-comma/multiline-always/test.ts.lint +++ b/test/rules/trailing-comma/multiline-always/test.ts.lint @@ -390,7 +390,7 @@ class Test { [ C, C, - ] + ], C ~ [Missing trailing comma] > @@ -509,7 +509,7 @@ interface ITest { [ C, C, - ] + ], C ~ [Missing trailing comma] > diff --git a/test/rules/trailing-comma/multiline-never/test.ts.fix b/test/rules/trailing-comma/multiline-never/test.ts.fix index 4240f80dbc5..1b2e7ef79cb 100644 --- a/test/rules/trailing-comma/multiline-never/test.ts.fix +++ b/test/rules/trailing-comma/multiline-never/test.ts.fix @@ -364,7 +364,7 @@ class Test { [ C, C - ] + ], C > >( @@ -430,7 +430,7 @@ interface ITest { [ C, C - ] + ], C > >( diff --git a/test/rules/trailing-comma/multiline-never/test.ts.lint b/test/rules/trailing-comma/multiline-never/test.ts.lint index 359d6063b36..bc6e2ed78be 100644 --- a/test/rules/trailing-comma/multiline-never/test.ts.lint +++ b/test/rules/trailing-comma/multiline-never/test.ts.lint @@ -390,7 +390,7 @@ class Test { C, C, ~ [Unnecessary trailing comma] - ] + ], C > >( @@ -462,7 +462,7 @@ interface ITest { C, C, ~ [Unnecessary trailing comma] - ] + ], C > >( diff --git a/test/tsconfig.json b/test/tsconfig.json index d672d8b5146..c6911ca7df6 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,11 +1,11 @@ { - "version": "2.0.3", + "version": "2.0.10", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "noUnusedParameters": true, "noUnusedLocals": true, - "sourceMap": false, + "sourceMap": true, "target": "es5", "outDir": "../build" }, diff --git a/test/utils.ts b/test/utils.ts index f3c80a751ee..7bf981a040c 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -15,6 +15,7 @@ */ import * as fs from "fs"; +import * as os from "os"; import * as path from "path"; import * as ts from "typescript"; @@ -82,3 +83,23 @@ export function assertContainsFailure(haystack: Lint.RuleFailure[], needle: Lint assert(false, "expected " + stringifiedNeedle + " within " + stringifiedHaystack); } } + +export function createTempFile(extension: string) { + let tmpfile: string; + for (let i = 0; i < 5; i++) { + const attempt = path.join(os.tmpdir(), `tslint.test${Math.round(Date.now() * Math.random())}.${extension}`); + if (!fs.existsSync(tmpfile)) { + tmpfile = attempt; + break; + } + } + if (tmpfile === undefined) { + throw new Error("Couldn't create temp file"); + } + return tmpfile; +} + +// converts Windows normalized paths (with backwards slash `\`) to paths used by TypeScript (with forward slash `/`) +export function denormalizeWinPath(path: string): string { + return path.replace(/\\/g, "/"); +}