diff --git a/CHANGELOG.md b/CHANGELOG.md index 858a39930bf..4c31e320107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ Change Log === +v3.1.0-dev.1 +--- +* [bugfix] fixed `no-shadowed-variable` false positives when handling destructuring in function params (#727) +- [enhancement] `rulesDirectory` in `tslint.json` now supports multiple file paths (#795) + +v3.0.0 +--- +* [bugfix] `member-access` rule now handles object literals and get/set accessors properly (#801) + * New rule options: `check-accessor` and `check-constructor` +* All the changes from the following releases, including some **breaking changes**: + * `3.0.0-dev.3` + * `3.0.0-dev.2` + * `3.0.0-dev.1` + * `2.6.0-dev.2` + * `2.6.0-dev.1` + v3.0.0-dev.3 --- * Typescript is now a peerDependency (#791) @@ -16,12 +32,12 @@ v3.0.0-dev.2 v3.0.0-dev.1 --- * **BREAKING CHANGES** - * Rearchitect tslint to use external modules instead of merged namespaces (#726) - * Dependencies need to be handled differently now by custom rules and formatters - * See the [PR](https://github.com/palantir/tslint/pull/726) for full details about this change - * `no-trailing-comma` rule removed, it is replaced by the `trailing-comma` rule (#687) - * Rename `sort-object-literal-keys` rule to `object-literal-sort-keys` (#304, #537) - * `Lint.abstract()` has been removed (#700) + * Rearchitect TSLint to use external modules instead of merged namespaces (#726) + * Dependencies need to be handled differently now by custom rules and formatters + * See the [PR](https://github.com/palantir/tslint/pull/726) for full details about this change + * `no-trailing-comma` rule removed, it is replaced by the `trailing-comma` rule (#687) + * Rename `sort-object-literal-keys` rule to `object-literal-sort-keys` (#304, #537) + * `Lint.abstract()` has been removed (#700) * [new-rule] `trailing-comma` rule (#557, #687) * [new-rule-option] "ban-keywords" option for `variable-name` rule (#735, #748) * [bugfix] `typedef` rule now handles `for-of` loops correctly (#743) @@ -42,7 +58,7 @@ v2.5.1 * [new-rule-option] "avoid-escape" option for quotemark rule (#543) * [bugfix] type declaration for tslint external module #686 * [enhancement] `AbstractRule` and `AbstractFormatter` are now abstract classes (#631) - * Note: `Lint.abstract()` is now deprecated + * Note: `Lint.abstract()` is now deprecated v2.5.0 --- @@ -112,7 +128,7 @@ v2.4.0 * [bug] fix error message in `no-var-keyword` rule * [enhancement] CI tests are now run on node v0.12 in addition to v0.10 * **BREAKING** - * `-f` option removed from CLI + * `-f` option removed from CLI v2.3.1-beta --- @@ -120,7 +136,7 @@ v2.3.1-beta * [new-rule] `no-require-imports` disallows `require()` style imports * [new-rule] `no-shadowed-variable` moves over shadowed variable checking from `no-duplicate-variable` into its own rule * **BREAKING** - * `no-duplicate-variable` now only checks for duplicates within the same block scope; enable `no-shadowed-variable` to get duplicate-variable checking across block scopes + * `no-duplicate-variable` now only checks for duplicates within the same block scope; enable `no-shadowed-variable` to get duplicate-variable checking across block scopes * [enhancement] `no-duplicate-variable`, `no-shadowed-variable`, and `no-use-before-declare` now support ES6 destructuring * [enhancement] tslint CLI now uses a default configuration if no config file is found @@ -137,8 +153,8 @@ v2.2.0-beta --- * Upgraded Typescript compiler to 1.5.0-beta * **BREAKING CHANGES** - * due to changes to the typescript compiler API, old custom rules may no longer work and may need to be rewritten - * the JSON formatter's line and character positions are now back to being 0-indexed instead of 1-indexed + * due to changes to the typescript compiler API, old custom rules may no longer work and may need to be rewritten + * the JSON formatter's line and character positions are now back to being 0-indexed instead of 1-indexed * [bugs] #328 #334 #319 #351 #365 #254 * [bug] fixes for tslint behavior around template strings (fixes #357, #349, #332, and more) * [new-rule] `align` rule now enforces vertical alignment on parameters, arguments, and statements @@ -160,16 +176,16 @@ v2.0.1 --- * Upgraded Typescript compiler to 1.4 * **BREAKING CHANGES** - * typedef rule options were modified: - * index-signature removed as no longer necessary - * property-signature renamed to property-declaration - * variable-declarator renamed to variable-declaration - * member-variable-declarator renamed to member-variable-declaration - * typedef-whitespace rule options were modified: - * catch-clause was removed as invalid - * further options were added, see readme for more details - * due to changes to the typescript compiler API, old custom rules may no longer work and may need to be rewritten - * the JSON formatter's line and character positions are now 1-indexed instead of 0-indexed + * typedef rule options were modified: + * index-signature removed as no longer necessary + * property-signature renamed to property-declaration + * variable-declarator renamed to variable-declaration + * member-variable-declarator renamed to member-variable-declaration + * typedef-whitespace rule options were modified: + * catch-clause was removed as invalid + * further options were added, see readme for more details + * due to changes to the typescript compiler API, old custom rules may no longer work and may need to be rewritten + * the JSON formatter's line and character positions are now 1-indexed instead of 0-indexed v1.2.0 --- @@ -183,10 +199,10 @@ v1.0.0 --- * upgrade TypeScript compiler to 1.3 * **BREAKING CHANGES** - * all error messages now start with a lower-case character and do not end with a period - * all rule options are consistent in nomenclature. The `typedef` and `typedef-whitespace` rules now take in hyphenated options - * `unused-variables` rule cannot find unused private variables defined in the constructor due to a bug in 1.3 compiler - * `indent` rule has changed to only check for tabs or spaces and not enforce indentation levels + * all error messages now start with a lower-case character and do not end with a period + * all rule options are consistent in nomenclature. The `typedef` and `typedef-whitespace` rules now take in hyphenated options + * `unused-variables` rule cannot find unused private variables defined in the constructor due to a bug in 1.3 compiler + * `indent` rule has changed to only check for tabs or spaces and not enforce indentation levels v0.4.12 --- diff --git a/README.md b/README.md index ad6bb662588..77df087f81e 100644 --- a/README.md +++ b/README.md @@ -14,84 +14,98 @@ Installation ##### CLI -```npm install tslint -g``` +``` +npm install tslint -g +npm install typescript -g +``` ##### Library -```npm install tslint``` +``` +npm install tslint +npm install typescript +``` -##### `next` distribution +##### Peer dependencies -The [`next` branch of the TSLint repo](https://github.com/palantir/tslint/tree/next) tracks the latest TypeScript -compiler and allows you to lint TS code that uses 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`. +The `typescript` module is a peer dependency of TSLint, which allows you to update the compiler independently from the +linter. This also means that `tslint` will have to use the same version of `tsc` used to actually compile your sources. + +Breaking changes in the latest dev release of `typescript@next` might break something in the linter if we haven't built against that release yet. If this happens to you, you can try: + +1. picking up `tslint@next`, which may have some bugfixes not released in `tslint@latest` + (see [release notes here](https://github.com/palantir/tslint/releases)). +2. rolling back `typescript` to a known working version. Usage ----- -Please first ensure that the TypeScript source files compile correctly. +Please ensure that the TypeScript source files compile correctly _before_ running the linter. ##### CLI - usage: `tslint [options] [file ...]` +usage: `tslint [options] [file ...]` - Options: +Options: - -c, --config configuration file - -o, --out output file - -r, --rules-dir rules directory - -s, --formatters-dir formatters directory - -t, --format output format (prose, json) [default: "prose"] +``` +-c, --config configuration file +-o, --out output file +-r, --rules-dir rules directory +-s, --formatters-dir formatters directory +-t, --format output format (prose, json) [default: "prose"] +``` By default, configuration is loaded from `tslint.json`, if it exists in the current path, or the user's home directory, in that order. tslint accepts the following command-line options: - -c, --config: - The location of the configuration file that tslint will use to - determine which rules are activated and what options to provide - 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 - 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 - as in the non-list case, and the rest of the list are options passed - to the rule that will determine what it checks for (such as number - of characters for the max-line-length rule, or what functions to ban - for the ban rule). - - -o, --out: - A filename to output the results to. By default, tslint outputs to - stdout, which is usually the console where you're running it from. - - -r, --rules-dir: - An additional rules directory, for user-created rules. - tslint will always check its default rules directory, in - node_modules/tslint/build/rules, before checking the user-provided - rules directory, so rules in the user-provided rules directory - with the same name as the base rules will not be loaded. - - -s, --formatters-dir: - An additional formatters directory, for user-created formatters. - Formatters are files that will format the tslint output, before - writing it to stdout or the file passed in --out. The default - directory, node_modules/tslint/build/formatters, will always be - checked first, so user-created formatters with the same names - as the base formatters will not be loaded. - - -t, --format: - The formatter to use to format the results of the linter before - outputting it to stdout or the file passed in --out. The core - formatters are prose (human readable) and json (machine readable), - and prose is the default if this option is not used. Additional - formatters can be added and used if the --formatters-dir option - is set. - - --help: - Prints this help message. +``` +-c, --config: + The location of the configuration file that tslint will use to + determine which rules are activated and what options to provide + 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 + 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 + as in the non-list case, and the rest of the list are options passed + to the rule that will determine what it checks for (such as number + of characters for the max-line-length rule, or what functions to ban + for the ban rule). + +-o, --out: + A filename to output the results to. By default, tslint outputs to + stdout, which is usually the console where you're running it from. + +-r, --rules-dir: + An additional rules directory, for user-created rules. + tslint will always check its default rules directory, in + node_modules/tslint/build/rules, before checking the user-provided + rules directory, so rules in the user-provided rules directory + with the same name as the base rules will not be loaded. + +-s, --formatters-dir: + An additional formatters directory, for user-created formatters. + Formatters are files that will format the tslint output, before + writing it to stdout or the file passed in --out. The default + directory, node_modules/tslint/build/formatters, will always be + checked first, so user-created formatters with the same names + as the base formatters will not be loaded. + +-t, --format: + The formatter to use to format the results of the linter before + outputting it to stdout or the file passed in --out. The core + formatters are prose (human readable) and json (machine readable), + and prose is the default if this option is not used. Additional + formatters can be added and used if the --formatters-dir option + is set. + +--help: + Prints this help message. +``` ##### Library @@ -129,19 +143,19 @@ A sample configuration file with all options is available [here](https://github. * `"parameters"` checks alignment of function parameters. * `"arguments"` checks alignment of function call arguments. * `"statements"` checks alignment of statements. -* `ban` bans the use of specific functions. Options are ["object", "function"] pairs that ban the use of object.function() +* `ban` bans the use of specific functions. Options are ["object", "function"] pairs that ban the use of object.function(). * `class-name` enforces PascalCased class and interface names. * `comment-format` enforces rules for single-line comments. Rule options: * `"check-space"` enforces the rule that all single-line comments must begin with a space, as in `// comment` * note that comments starting with `///` are also allowed, for things such as `///` - * `"check-lowercase"` enforces the rule that the first non-whitespace character of a comment must be lowercase, if applicable - * `"check-uppercase"` enforces the rule that the first non-whitespace character of a comment must be uppercase, if applicable + * `"check-lowercase"` enforces the rule that the first non-whitespace character of a comment must be lowercase, if applicable. + * `"check-uppercase"` enforces the rule that the first non-whitespace character of a comment must be uppercase, if applicable. * `curly` enforces braces for `if`/`for`/`do`/`while` statements. * `eofline` enforces the file to end with a newline. * `forin` enforces a `for ... in` statement to be filtered with an `if` statement.* * `indent` enforces indentation with tabs or spaces. Rule options (one is required): - * `"tabs"` enforces consistent tabs - * `"spaces"` enforces consistent spaces + * `"tabs"` enforces consistent tabs. + * `"spaces"` enforces consistent spaces. * `interface-name` enforces the rule that interface names must begin with a capital 'I' * `jsdoc-format` enforces basic format rules for jsdoc comments -- comments starting with `/**` * each line contains an asterisk and asterisks must be aligned @@ -152,10 +166,12 @@ A sample configuration file with all options is available [here](https://github. * `label-undefined` checks that labels are defined before usage. * `max-line-length` sets the maximum length of a line. * `member-access` enforces using explicit visibility on class members + * `"check-accessor"` enforces explicit visibility on get/set accessors (can only be public) + * `"check-constructor"` enforces explicit visibility on constructors (can only be public) * `member-ordering` enforces member ordering. Rule options: - * `public-before-private` All public members must be declared before private members - * `static-before-instance` All static members must be declared before instance members - * `variables-before-functions` All variables needs to be declared before functions + * `public-before-private` All public members must be declared before private members. + * `static-before-instance` All static members must be declared before instance members. + * `variables-before-functions` All variables needs to be declared before functions. * `no-any` diallows usages of `any` as a type decoration. * `no-arg` disallows access to `arguments.callee`. * `no-bitwise` disallows bitwise operators. @@ -169,70 +185,69 @@ A sample configuration file with all options is available [here](https://github. * `no-duplicate-variable` disallows duplicate variable declarations in the same block scope. * `no-empty` disallows empty blocks. * `no-eval` disallows `eval` function invocations. -* `no-inferrable-types` disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean -* `no-internal-module` disallows internal `module`, use `namespace` instead. -* `no-require-imports` disallows require() style imports. +* `no-inferrable-types` disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. +* `no-internal-module` disallows internal `module` (use `namespace` instead). +* `no-require-imports` disallows `require()` style imports (use ES6-style imports instead). * `no-shadowed-variable` disallows shadowed variable declarations. * `no-string-literal` disallows object access via string literals. * `no-switch-case-fall-through` disallows falling through case statements. * `no-trailing-whitespace` disallows trailing whitespace at the end of a line. * `no-unreachable` disallows unreachable code after `break`, `catch`, `throw`, and `return` statements. * `no-unused-expression` disallows unused expression statements, that is, expression statements that are not assignments or function invocations (and thus no-ops). -* `no-unused-variable` disallows unused imports, variables, functions and private class members. +* `no-unused-variable` disallows unused imports, variables, functions and private class members. Rule options: * `"check-parameters"` disallows unused function and constructor parameters. * NOTE: this option is experimental and does not work with classes that use abstract method declarations, among other things. Use at your own risk. - * `"react"` relaxes the rule for a namespace import named `React` (from either the module `"react"` or `"react/addons"`) to also consider JSX expressions - uses of the import + * `"react"` relaxes the rule for a namespace import named `React` (from either the module `"react"` or `"react/addons"`). Any JSX expression in the file will be treated as a usage of `React` (because it expands to `React.createElement`). * `no-use-before-declare` disallows usage of variables before their declaration. * `no-var-keyword` disallows usage of the `var` keyword, use `let` or `const` instead. -* `no-var-requires` disallows the use of require statements except in import statements, banning the use of forms such as `var module = require("module")` -* `object-literal-sort-keys` checks that keys in object literals are declared in alphabetical order +* `no-var-requires` disallows the use of require statements except in import statements, banning the use of forms such as `var module = require("module")`. +* `object-literal-sort-keys` checks that keys in object literals are declared in alphabetical order (useful to prevent merge conflicts). * `one-line` enforces the specified tokens to be on the same line as the expression preceding it. Rule options: - * `"check-catch"` checks that `catch` is on the same line as the closing brace for `try` - * `"check-else"` checks that `else` is on the same line as the closing brace for `if` + * `"check-catch"` checks that `catch` is on the same line as the closing brace for `try`. + * `"check-else"` checks that `else` is on the same line as the closing brace for `if`. * `"check-open-brace"` checks that an open brace falls on the same line as its preceding expression. * `"check-whitespace"` checks preceding whitespace for the specified tokens. * `quotemark` enforces consistent single or double quoted string literals. Rule options (at least one of `"double"` or `"single"` is required): - * `"single"` enforces single quotes - * `"double"` enforces double quotes + * `"single"` enforces single quotes. + * `"double"` enforces double quotes. * `"avoid-escape"` allows you to use the "other" quotemark in cases where escaping would normally be required. For example, `[true, "double", "avoid-escape"]` would not report a failure on the string literal `'Hello "World"'`. -* `radix` enforces the radix parameter of `parseInt` +* `radix` enforces the radix parameter of `parseInt`. * `semicolon` enforces semicolons at the end of every statement. * `switch-default` enforces a `default` case in `switch` statements. * `trailing-comma` enforces or disallows trailing comma within array and object literals, destructuring assignment and named imports. Each rule option requires a value of `"always"` or `"never"`. Rule options: - * `"multiline"` checks multi-line object literals - * `"singleline"` checks single-line object literals + * `"multiline"` checks multi-line object literals. + * `"singleline"` checks single-line object literals. * `triple-equals` enforces === and !== in favor of == and !=. * `typedef` enforces type definitions to exist. Rule options: - * `"call-signature"` checks return type of functions - * `"parameter"` checks type specifier of function parameters - * `"property-declaration"` checks return types of interface properties - * `"variable-declaration"` checks variable declarations - * `"member-variable-declaration"` checks member variable declarations + * `"call-signature"` checks return type of functions. + * `"parameter"` checks type specifier of function parameters. + * `"property-declaration"` checks return types of interface properties. + * `"variable-declaration"` checks variable declarations. + * `"member-variable-declaration"` checks member variable declarations. * `typedef-whitespace` enforces spacing whitespace for type definitions. Each rule option requires a value of `"space"` or `"nospace"` to require a space or no space before the type specifier's colon. Rule options: - * `"call-signature"` checks return type of functions - * `"index-signature"` checks index type specifier of indexers - * `"parameter"` checks function parameters - * `"property-declaration"` checks object property declarations - * `"variable-declaration"` checks variable declaration -* `use-strict` enforces ECMAScript 5's strict mode - * `check-module` checks that all top-level modules are using strict mode - * `check-function` checks that all top-level functions are using strict mode + * `"call-signature"` checks return type of functions. + * `"index-signature"` checks index type specifier of indexers. + * `"parameter"` checks function parameters. + * `"property-declaration"` checks object property declarations. + * `"variable-declaration"` checks variable declaration. +* `use-strict` enforces ECMAScript 5's strict mode. + * `check-module` checks that all top-level modules are using strict mode. + * `check-function` checks that all top-level functions are using strict mode. * `variable-name` checks variables names for various errors. Rule options: * `"check-format"`: allows only camelCased or UPPER_CASED variable names - * `"allow-leading-underscore"` allows underscores at the beginnning - * `"allow-trailing-underscore"` allows underscores at the end - * `"ban-keywords"`: disallows the use of certain TypeScript keywords (`any`, `Number`, `number`, `String`, `string`, `Boolean`, `boolean`, `undefined`) as variable or parameter names + * `"allow-leading-underscore"` allows underscores at the beginning. + * `"allow-trailing-underscore"` allows underscores at the end. + * `"ban-keywords"`: disallows the use of certain TypeScript keywords (`any`, `Number`, `number`, `String`, `string`, `Boolean`, `boolean`, `undefined`) as variable or parameter names. * `whitespace` enforces spacing whitespace. Rule options: - * `"check-branch"` checks branching statements (`if`/`else`/`for`/`while`) are followed by whitespace - * `"check-decl"`checks that variable declarations have whitespace around the equals token - * `"check-operator"` checks for whitespace around operator tokens - * `"check-module"` checks for whitespace in import & export statements - * `"check-separator"` checks for whitespace after separator tokens (`,`/`;`) - * `"check-type"` checks for whitespace before a variable type specification - * `"check-typecast"` checks for whitespace between a typecast and its target + * `"check-branch"` checks branching statements (`if`/`else`/`for`/`while`) are followed by whitespace. + * `"check-decl"`checks that variable declarations have whitespace around the equals token. + * `"check-operator"` checks for whitespace around operator tokens. + * `"check-module"` checks for whitespace in import & export statements. + * `"check-separator"` checks for whitespace after separator tokens (`,`/`;`). + * `"check-type"` checks for whitespace before a variable type specification. + * `"check-typecast"` checks for whitespace between a typecast and its target. TSLint Rule Flags ----- @@ -247,17 +262,27 @@ Rules flags enable or disable rules as they are parsed. A rule is enabled or dis For example, imagine the directive `/* tslint:disable */` on the first line of a file, `/* tslint:enable:ban class-name */` on the 10th line and `/* tslint:enable */` on the 20th. No rules will be checked between the 1st and 10th lines, only the `ban` and `class-name` rules will be checked between the 10th and 20th, and all rules will be checked for the remainder of the file. -Custom Rules +Custom Rules (from the TypeScript community) +--------------- + +If we don't have all the rules you're looking for, you can either write your own custom rules or use custom rules that others have developed. The repos below are a good source of custom rules: + +- [ESLint rules for TSLint](https://github.com/buzinas/tslint-eslint-rules) - Improve your TSLint with the missing ESLint Rules +- [tslint-microsoft-contrib](https://github.com/Microsoft/tslint-microsoft-contrib) - A set of TSLint rules used on some Microsoft projects + +Writing Custom Rules ------------ 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 dasherized forms, so `"no-imports": true` would turn on the rule. +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. -Now, let us first write the rule in TypeScript. At the top, we reference TSLint's [definition file](https://github.com/palantir/tslint/blob/master/lib/tslint.d.ts) and the [definition file](https://github.com/palantir/tslint/blob/master/typings/typescriptServices.d.ts) for TypeScript's language services. 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`. ```typescript -/// -/// +import * as ts from "typescript"; +import * as Lint from "tslint/lib/lint"; export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "import statement forbidden"; @@ -266,11 +291,8 @@ export class Rule extends Lint.Rules.AbstractRule { return this.applyWithWalker(new NoImportsWalker(sourceFile, this.getOptions())); } } -``` -The walker takes care of all the work. - -```typescript +// The walker takes care of all the work. class NoImportsWalker extends Lint.RuleWalker { public visitImportDeclaration(node: ts.ImportDeclaration) { // create a failure at the current position @@ -286,45 +308,18 @@ 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`: `tsc -m commonjs --noImplicitAny noImportsRule.ts tslint.d.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! -Now, let us rewrite the same rule in Javascript. +Final notes: -```javascript -function Rule() { - Lint.Rules.AbstractRule.apply(this, arguments); -} - -Rule.prototype = Object.create(Lint.Rules.AbstractRule.prototype); -Rule.prototype.apply = function(sourceFile) { - return this.applyWithWalker(new NoImportsWalker(sourceFile, this.getOptions())); -}; - -function NoImportsWalker() { - Lint.RuleWalker.apply(this, arguments); -} - -NoImportsWalker.prototype = Object.create(Lint.RuleWalker.prototype); -NoImportsWalker.prototype.visitImportDeclaration = function (node) { - // create a failure at the current position - this.addFailure(this.createFailure(node.getStart(), node.getWidth(), "import statement forbidden")); - - // call the base version of this visitor to actually parse this node - Lint.RuleWalker.prototype.visitImportDeclaration.call(this, node); -}; - -exports.Rule = Rule; -``` - -As you can see, it's a pretty straightforward translation from the equivalent TypeScript code. +- Core rules cannot be overwritten with a custom implementation. +- Custom rules can also take in options just like core rules (retrieved via `this.getOptions()`). -Finally, core rules cannot be overwritten with a custom implementation, and rules can also take in options (retrieved via `this.getOptions()`). - -Custom Formatters +Writing Custom Formatters ----------------- Just like rules, additional formatters can also be supplied to TSLint via `--formatters-dir` on the CLI or `formattersDirectory` option on the library or `grunt-tslint`. Writing a new formatter is simpler than writing a new rule, as shown in the JSON formatter's code. ```typescript -/// -/// +import * as ts from "typescript"; +import * as Lint from "tslint/lib/lint"; export class Formatter extends Lint.Formatters.AbstractFormatter { public format(failures: Lint.RuleFailure[]): string { @@ -347,11 +342,18 @@ npm install grunt ``` +#### `next` branch + +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`. + Creating a new Release ---------------------- -1. Bump up the version number in package.json and tslint.ts -2. Add a section for the new release in CHANGELOG.md +1. Bump up the version number in `package.json` and `tslint.ts` +2. Add a section for the new release in `CHANGELOG.md` 3. Run `grunt` to build the latest sources 4. Commit 5. Run `npm publish` diff --git a/package.json b/package.json index fc54bf01e1c..6e8be109f1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "3.0.0-dev.3", + "version": "3.1.0-dev.1", "description": "a static analysis linter for TypeScript", "bin": { "tslint": "./bin/tslint" @@ -35,7 +35,7 @@ "grunt-ts": "^5.1.0", "grunt-tslint": "latest", "mocha": "^2.2.5", - "tslint": "latest", + "tslint": "next", "typescript": "next" }, "peerDependencies": { diff --git a/src/language/utils.ts b/src/language/utils.ts index 50dbac8a7c3..9fdba79c8c9 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -97,16 +97,21 @@ export function isBlockScopedVariable(node: ts.VariableDeclaration | ts.Variable } export function isBlockScopedBindingElement(node: ts.BindingElement): boolean { + const variableDeclaration = getBindingElementVariableDeclaration(node); + // if no variable declaration, it must be a function param, which is block scoped + return (variableDeclaration == null) || isBlockScopedVariable(variableDeclaration); +} + +export function getBindingElementVariableDeclaration(node: ts.BindingElement): ts.VariableDeclaration { let currentParent = node.parent; while (currentParent.kind !== ts.SyntaxKind.VariableDeclaration) { if (currentParent.parent == null) { - // if we didn't encounter a VariableDeclaration, this must be a function parameter, which is block scoped - return true; + return null; // function parameter, no variable declaration } else { currentParent = currentParent.parent; } } - return isBlockScopedVariable( currentParent); + return currentParent; } /** diff --git a/src/rules/memberAccessRule.ts b/src/rules/memberAccessRule.ts index ce4288fafec..532fd916602 100644 --- a/src/rules/memberAccessRule.ts +++ b/src/rules/memberAccessRule.ts @@ -30,15 +30,44 @@ export class MemberAccessWalker extends Lint.RuleWalker { super(sourceFile, options); } + public visitConstructorDeclaration(node: ts.ConstructorDeclaration) { + if (this.hasOption("check-constructor")) { + // constructor is only allowed to have public or nothing, but the compiler will catch this + this.validateVisibilityModifiers(node); + } + + super.visitConstructorDeclaration(node); + } + public visitMethodDeclaration(node: ts.MethodDeclaration) { this.validateVisibilityModifiers(node); + super.visitMethodDeclaration(node); } public visitPropertyDeclaration(node: ts.PropertyDeclaration) { this.validateVisibilityModifiers(node); + super.visitPropertyDeclaration(node); + } + + public visitGetAccessor(node: ts.AccessorDeclaration) { + if (this.hasOption("check-accessor")) { + this.validateVisibilityModifiers(node); + } + super.visitGetAccessor(node); + } + + public visitSetAccessor(node: ts.AccessorDeclaration) { + if (this.hasOption("check-accessor")) { + this.validateVisibilityModifiers(node); + } + super.visitSetAccessor(node); } private validateVisibilityModifiers(node: ts.Node) { + if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) { + return; + } + const hasAnyVisibilityModifiers = Lint.hasModifier( node.modifiers, ts.SyntaxKind.PublicKeyword, diff --git a/src/rules/noShadowedVariableRule.ts b/src/rules/noShadowedVariableRule.ts index f105595a863..46b51a40975 100644 --- a/src/rules/noShadowedVariableRule.ts +++ b/src/rules/noShadowedVariableRule.ts @@ -36,10 +36,15 @@ class NoShadowedVariableWalker extends Lint.BlockScopeAwareRuleWalker node.name, isBlockScoped); + if (variableDeclaration) { + const isBlockScopedVariable = Lint.isBlockScopedVariable(variableDeclaration); + this.handleSingleVariableIdentifier( node.name, isBlockScopedVariable); + } else { + this.handleSingleParameterIdentifier( node.name); + } } super.visitBindingElement(node); @@ -64,15 +69,11 @@ class NoShadowedVariableWalker extends Lint.BlockScopeAwareRuleWalker node.name; - const variableName = variableIdentifier.text; - const currentScope = this.getCurrentScope(); + const isSingleParameter = node.name.kind === ts.SyntaxKind.Identifier; - if (this.isVarInAnyScope(variableName)) { - this.addFailureOnIdentifier(variableIdentifier); + if (isSingleParameter) { + this.handleSingleParameterIdentifier( node.name); } - currentScope.varNames.push(variableName); super.visitParameterDeclaration(node); } @@ -112,6 +113,17 @@ class NoShadowedVariableWalker extends Lint.BlockScopeAwareRuleWalker scopeInfo.varNames.indexOf(varName) >= 0); } diff --git a/src/tsconfig.json b/src/tsconfig.json index d37238b8b3c..3155a7f22ae 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -8,6 +8,9 @@ "target": "es5", "outDir": "../lib" }, + "atom": { + "rewriteTsconfig": false + }, "filesGlob": [ "../typings/tsd.d.ts", "./*.ts", @@ -98,4 +101,4 @@ "rules/variableNameRule.ts", "rules/whitespaceRule.ts" ] -} \ No newline at end of file +} diff --git a/src/tslint.ts b/src/tslint.ts index 2d60b201271..266cf59fc60 100644 --- a/src/tslint.ts +++ b/src/tslint.ts @@ -20,7 +20,7 @@ import {findConfiguration as config} from "./configuration"; const moduleDirectory = path.dirname(module.filename); class Linter { - public static VERSION = "3.0.0-dev.3"; + public static VERSION = "3.1.0-dev.1"; public static findConfiguration = config; private fileName: string; diff --git a/test/files/rules/memberaccess-accessor.test.ts b/test/files/rules/memberaccess-accessor.test.ts new file mode 100644 index 00000000000..f664fa7f324 --- /dev/null +++ b/test/files/rules/memberaccess-accessor.test.ts @@ -0,0 +1,17 @@ +class Members { + get g() { + return 1; + } + set s(o: any) {} + + public get publicG() { + return 1; + } + public set publicS(o: any) {} +} + +const obj = { + get g() { + return 1; + } +}; diff --git a/test/files/rules/memberaccess-constructor.test.ts b/test/files/rules/memberaccess-constructor.test.ts new file mode 100644 index 00000000000..4f20fd4fc2b --- /dev/null +++ b/test/files/rules/memberaccess-constructor.test.ts @@ -0,0 +1,9 @@ +class ContructorsNoAccess { + constructor(i: number); + constructor(o: any) {} +} + +class ContructorsAccess { + public constructor(i: number); + public constructor(o: any) {} +} diff --git a/test/files/rules/memberaccess.test.ts b/test/files/rules/memberaccess.test.ts index 0800b0c4d0a..cff562c6672 100644 --- a/test/files/rules/memberaccess.test.ts +++ b/test/files/rules/memberaccess.test.ts @@ -1,18 +1,32 @@ -class Foo { - constructor() { - } +declare class AmbientNoAccess { + a(): number; +} - public w: number; - private x: number; - protected y: number; - z: number; +declare class AmbientAccess { + public a(): number; +} - public barW(): any { - } - private barX(): any { - } - protected barY(): any { - } - barZ(): any { +class Members { + i: number; + + public nPublic: number; + protected nProtected: number; + private nPrivate: number; + + noAccess(x: number): number; + noAccess(o: any): any {} + + public access(x: number): number; + public access(o: any): any {} +} + +const obj = { + func() {} +}; + +function main() { + class A { + i: number; + public n: number; } } diff --git a/test/files/rules/no-shadowed-variable.test.ts b/test/files/rules/no-shadowed-variable.test.ts index 126727bcb61..36b63434301 100644 --- a/test/files/rules/no-shadowed-variable.test.ts +++ b/test/files/rules/no-shadowed-variable.test.ts @@ -114,3 +114,17 @@ interface FalsePositive4 { (parameters: T, runSynchonous: boolean): TResult; (parameters: T, callback: (error: Error, result: TResult) => void): void; } + +let p = 1; +function testParameterDestructuring( + { pos: p }: FalsePositive3, // failure + { pos: q, specular: r }: FalsePositive3, + { pos }: FalsePositive3 +) { + p = 2; + var q = 1; // failure + + function someInnerFunc() { + let pos = 3; // failure + } +} diff --git a/test/formatters/jsonFormatterTests.ts b/test/formatters/jsonFormatterTests.ts index 246b86c6f29..04fc3d40694 100644 --- a/test/formatters/jsonFormatterTests.ts +++ b/test/formatters/jsonFormatterTests.ts @@ -36,7 +36,7 @@ describe("JSON Formatter", () => { new Lint.RuleFailure(sourceFile, 0, maxPosition, "full failure", "full-name") ]; - /* tslint:disable:sort-object-literal-keys */ + /* tslint:disable:object-literal-sort-keys */ const expectedResult = [{ name: TEST_FILE, failure: "first failure", @@ -82,7 +82,7 @@ describe("JSON Formatter", () => { }, ruleName: "full-name" }]; - /* tslint:enable:sort-object-literal-keys */ + /* tslint:enable:object-literal-sort-keys */ const actualResult = JSON.parse(formatter.format(failures)); assert.deepEqual(actualResult, expectedResult); diff --git a/test/rules/memberAccessTests.ts b/test/rules/memberAccessTests.ts index fc2920813c9..dc6d6b651d1 100644 --- a/test/rules/memberAccessTests.ts +++ b/test/rules/memberAccessTests.ts @@ -16,14 +16,47 @@ import * as Lint from "../lint"; describe("", () => { - it("enforces using explicit visibility on class members", () => { - let fileName = "rules/memberaccess.test.ts"; - let MemberAccessRule = Lint.Test.getRule("member-access"); - let actualFailures = Lint.Test.applyRuleOnFile(fileName, MemberAccessRule); - - Lint.Test.assertFailuresEqual(actualFailures, [ - Lint.Test.createFailure(fileName, [8, 5], [8, 15], MemberAccessRule.FAILURE_STRING), - Lint.Test.createFailure(fileName, [16, 5], [17, 6], MemberAccessRule.FAILURE_STRING) - ]); + it("ensures that class properties have access modifiers", () => { + const fileName = "rules/memberaccess.test.ts"; + const expectedFailures = [ + [[2, 5], [2, 17]], + [[10, 5], [10, 15]], + [[16, 5], [16, 33]], + [[17, 5], [17, 29]], + [[29, 9], [29, 19]] + ]; + + assertFailuresInFile(fileName, expectedFailures); }); + + it("ensures that constructors have access modifiers", () => { + const fileName = "rules/memberaccess-constructor.test.ts"; + const expectedFailures = [ + [[2, 5], [2, 28]], + [[3, 5], [3, 27]] + ]; + const options = [true, "check-constructor"]; + + assertFailuresInFile(fileName, expectedFailures, options); + }); + + it("ensures that accessors have access modifiers", () => { + const fileName = "rules/memberaccess-accessor.test.ts"; + const expectedFailures = [ + [[2, 5], [4, 6]], + [[5, 5], [5, 21]] + ]; + const options = [true, "check-accessor"]; + + assertFailuresInFile(fileName, expectedFailures, options); + }); + + function assertFailuresInFile(fileName: string, expectedFailures: number[][][], options: any[] = [true]) { + const MemberAccessRule = Lint.Test.getRule("member-access"); + const createFailure = Lint.Test.createFailuresOnFile(fileName, MemberAccessRule.FAILURE_STRING); + const expectedFileFailures = expectedFailures.map(failure => createFailure(failure[0], failure[1])); + + const actualFailures = Lint.Test.applyRuleOnFile(fileName, MemberAccessRule, options); + Lint.Test.assertFailuresEqual(actualFailures, expectedFileFailures); + } }); diff --git a/test/rules/noShadowedVariableRuleTests.ts b/test/rules/noShadowedVariableRuleTests.ts index ba24cf86131..3d8911fca60 100644 --- a/test/rules/noShadowedVariableRuleTests.ts +++ b/test/rules/noShadowedVariableRuleTests.ts @@ -41,7 +41,10 @@ describe("", () => { Lint.Test.createFailure(fileName, [90, 16], [90, 17], NoShadowedVariableRule.FAILURE_STRING + "z'"), Lint.Test.createFailure(fileName, [94, 15], [94, 16], NoShadowedVariableRule.FAILURE_STRING + "x'"), Lint.Test.createFailure(fileName, [95, 15], [95, 16], NoShadowedVariableRule.FAILURE_STRING + "y'"), - Lint.Test.createFailure(fileName, [97, 17], [97, 18], NoShadowedVariableRule.FAILURE_STRING + "z'") + Lint.Test.createFailure(fileName, [97, 17], [97, 18], NoShadowedVariableRule.FAILURE_STRING + "z'"), + Lint.Test.createFailure(fileName, [120, 12], [120, 13], NoShadowedVariableRule.FAILURE_STRING + "p'"), + Lint.Test.createFailure(fileName, [125, 9], [125, 10], NoShadowedVariableRule.FAILURE_STRING + "q'"), + Lint.Test.createFailure(fileName, [128, 13], [128, 16], NoShadowedVariableRule.FAILURE_STRING + "pos'") ]; const actualFailures = Lint.Test.applyRuleOnFile(fileName, NoShadowedVariableRule); diff --git a/test/tsconfig.json b/test/tsconfig.json index ee210e73559..cb7128c529e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -7,6 +7,9 @@ "target": "es5", "outDir": "../build" }, + "atom": { + "rewriteTsconfig": false + }, "filesGlob": [ "../typings/tsd.d.ts", "./typings/**/*.d.ts", @@ -166,4 +169,4 @@ "formatters/proseFormatterTests.ts", "formatters/verboseFormatterTests.ts" ] -} \ No newline at end of file +} diff --git a/test/tsxTests.ts b/test/tsxTests.ts index 3a7ac546cb4..a2b32f6c8b6 100644 --- a/test/tsxTests.ts +++ b/test/tsxTests.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import * as Lint from "./lint"; describe("TSX syntax", () => { diff --git a/tslint.json b/tslint.json index f2a56af03ee..5678fb131c7 100644 --- a/tslint.json +++ b/tslint.json @@ -1,7 +1,7 @@ { "rules": { "class-name": true, - "comment-format": [true, "check-lowercase", "check-space"], + "comment-format": [true, "check-space"], "curly": true, "eofline": true, "forin": true,