Comparing version 0.4.6 to 0.4.7
@@ -27,2 +27,8 @@ var __extends = this.__extends || function (d, b) { | ||
UnreachableWalker.prototype.visitNode = function (node) { | ||
var previousReturned = this.hasReturned; | ||
if (node.kind() === 129 /* FunctionDeclaration */) { | ||
this.hasReturned = false; | ||
} | ||
if (this.hasReturned) { | ||
@@ -35,6 +41,9 @@ this.hasReturned = false; | ||
_super.prototype.visitNode.call(this, node); | ||
if (node.kind() === 129 /* FunctionDeclaration */) { | ||
this.hasReturned = previousReturned; | ||
} | ||
}; | ||
UnreachableWalker.prototype.visitBlock = function (node) { | ||
this.hasReturned = false; | ||
_super.prototype.visitBlock.call(this, node); | ||
@@ -45,3 +54,2 @@ this.hasReturned = false; | ||
UnreachableWalker.prototype.visitCaseSwitchClause = function (node) { | ||
this.hasReturned = false; | ||
_super.prototype.visitCaseSwitchClause.call(this, node); | ||
@@ -52,3 +60,2 @@ this.hasReturned = false; | ||
UnreachableWalker.prototype.visitDefaultSwitchClause = function (node) { | ||
this.hasReturned = false; | ||
_super.prototype.visitDefaultSwitchClause.call(this, node); | ||
@@ -59,3 +66,2 @@ this.hasReturned = false; | ||
UnreachableWalker.prototype.visitIfStatement = function (node) { | ||
this.hasReturned = false; | ||
_super.prototype.visitIfStatement.call(this, node); | ||
@@ -66,3 +72,2 @@ this.hasReturned = false; | ||
UnreachableWalker.prototype.visitElseClause = function (node) { | ||
this.hasReturned = false; | ||
_super.prototype.visitElseClause.call(this, node); | ||
@@ -69,0 +74,0 @@ this.hasReturned = false; |
@@ -35,4 +35,6 @@ var __extends = this.__extends || function (d, b) { | ||
NoUnusedVariablesWalker.prototype.visitImportDeclaration = function (node) { | ||
var position = this.positionAfter(node.importKeyword); | ||
this.validateReferencesForVariable(node.identifier.text(), position); | ||
if (!this.hasModifier(node.modifiers, 47 /* ExportKeyword */)) { | ||
var position = this.positionAfter(node.importKeyword); | ||
this.validateReferencesForVariable(node.identifier.text(), position); | ||
} | ||
_super.prototype.visitImportDeclaration.call(this, node); | ||
@@ -64,3 +66,3 @@ }; | ||
NoUnusedVariablesWalker.prototype.visitVariableStatement = function (node) { | ||
if (this.hasModifier(node.modifiers, "export")) { | ||
if (this.hasModifier(node.modifiers, 47 /* ExportKeyword */)) { | ||
this.skipVariableDeclaration = true; | ||
@@ -77,3 +79,3 @@ } | ||
if (!this.hasModifier(node.modifiers, "export")) { | ||
if (!this.hasModifier(node.modifiers, 47 /* ExportKeyword */)) { | ||
this.validateReferencesForVariable(variableName, position); | ||
@@ -89,3 +91,3 @@ } | ||
if (!this.hasModifier(node.modifiers, "public") && !this.skipParameterDeclaration && this.hasOption(OPTION_CHECK_PARAMETERS)) { | ||
if (!this.hasModifier(node.modifiers, 57 /* PublicKeyword */) && !this.skipParameterDeclaration && this.hasOption(OPTION_CHECK_PARAMETERS)) { | ||
this.validateReferencesForVariable(variableName, position); | ||
@@ -104,3 +106,3 @@ } | ||
if (this.hasModifier(modifiers, "public")) { | ||
if (this.hasModifier(modifiers, 57 /* PublicKeyword */)) { | ||
this.skipVariableDeclaration = true; | ||
@@ -118,3 +120,3 @@ } | ||
if (this.hasModifier(modifiers, "private")) { | ||
if (this.hasModifier(modifiers, 55 /* PrivateKeyword */)) { | ||
this.validateReferencesForVariable(variableName, position); | ||
@@ -126,10 +128,7 @@ } | ||
NoUnusedVariablesWalker.prototype.hasModifier = function (modifiers, text) { | ||
NoUnusedVariablesWalker.prototype.hasModifier = function (modifiers, modifierKind) { | ||
for (var i = 0, n = modifiers.childCount(); i < n; i++) { | ||
var modifier = modifiers.childAt(i); | ||
if (modifier.isToken()) { | ||
var modifierText = modifier.text(); | ||
if (modifierText === text) { | ||
return true; | ||
} | ||
if (modifier.kind() === modifierKind) { | ||
return true; | ||
} | ||
@@ -136,0 +135,0 @@ } |
@@ -30,3 +30,10 @@ var __extends = this.__extends || function (d, b) { | ||
this.languageServices = languageServices; | ||
this.classStartPosition = 0; | ||
} | ||
NoUseBeforeDeclareWalker.prototype.visitClassDeclaration = function (node) { | ||
this.classStartPosition = this.position(); | ||
_super.prototype.visitClassDeclaration.call(this, node); | ||
this.classStartPosition = 0; | ||
}; | ||
NoUseBeforeDeclareWalker.prototype.visitImportDeclaration = function (node) { | ||
@@ -49,3 +56,3 @@ var position = this.positionAfter(node.importKeyword); | ||
var referencePosition = reference.minChar; | ||
if (referencePosition < position) { | ||
if (_this.classStartPosition <= referencePosition && referencePosition < position) { | ||
var failureString = Rule.FAILURE_STRING_PREFIX + name + Rule.FAILURE_STRING_POSTFIX; | ||
@@ -52,0 +59,0 @@ var failure = _this.createFailure(referencePosition, name.length, failureString); |
@@ -114,2 +114,3 @@ var __extends = this.__extends || function (d, b) { | ||
} | ||
return precedingChild; | ||
@@ -116,0 +117,0 @@ }; |
@@ -28,4 +28,5 @@ var __extends = this.__extends || function (d, b) { | ||
__extends(WhitespaceWalker, _super); | ||
function WhitespaceWalker() { | ||
_super.apply(this, arguments); | ||
function WhitespaceWalker(syntaxTree, options) { | ||
_super.call(this, syntaxTree, options); | ||
this.lastPosition = this.getSyntaxTree().sourceUnit().fullWidth(); | ||
} | ||
@@ -54,2 +55,39 @@ WhitespaceWalker.prototype.visitToken = function (token) { | ||
WhitespaceWalker.prototype.visitSimpleArrowFunctionExpression = function (node) { | ||
var position = this.positionAfter(node.identifier); | ||
this.checkEqualsGreaterThan(node.equalsGreaterThanToken, node.identifier, position); | ||
_super.prototype.visitSimpleArrowFunctionExpression.call(this, node); | ||
}; | ||
WhitespaceWalker.prototype.visitParenthesizedArrowFunctionExpression = function (node) { | ||
var position = this.positionAfter(node.callSignature); | ||
this.checkEqualsGreaterThan(node.equalsGreaterThanToken, node.callSignature, position); | ||
_super.prototype.visitParenthesizedArrowFunctionExpression.call(this, node); | ||
}; | ||
WhitespaceWalker.prototype.visitConstructorType = function (node) { | ||
var position = this.positionAfter(node.newKeyword, node.typeParameterList, node.parameterList); | ||
this.checkEqualsGreaterThan(node.equalsGreaterThanToken, node.parameterList, position); | ||
_super.prototype.visitConstructorType.call(this, node); | ||
}; | ||
WhitespaceWalker.prototype.visitFunctionType = function (node) { | ||
var position = this.positionAfter(node.typeParameterList, node.parameterList); | ||
this.checkEqualsGreaterThan(node.equalsGreaterThanToken, node.parameterList, position); | ||
_super.prototype.visitFunctionType.call(this, node); | ||
}; | ||
WhitespaceWalker.prototype.checkEqualsGreaterThan = function (equalsGreaterThanToken, previousNode, position) { | ||
if (this.hasOption(OPTION_OPERATOR)) { | ||
this.checkForLeadingSpace(position, previousNode.trailingTrivia()); | ||
position += equalsGreaterThanToken.fullWidth(); | ||
this.checkForLeadingSpace(position, equalsGreaterThanToken.trailingTrivia()); | ||
} | ||
}; | ||
WhitespaceWalker.prototype.visitConditionalExpression = function (node) { | ||
@@ -112,2 +150,5 @@ if (this.hasOption(OPTION_OPERATOR)) { | ||
var failure = null; | ||
if (position === this.lastPosition) { | ||
return; | ||
} | ||
@@ -114,0 +155,0 @@ if (trivia.count() < 1) { |
Change Log | ||
=== | ||
v0.4.7 | ||
--- | ||
* [new-rule] added `no-unused-expression` rule which disallows unused expression statements | ||
* [feature] the `check-operator` option for the `whitespace` rule now checks whitespace around the => token | ||
* [bug] `no-use-before-declare-rule` no longer triggers false positives for member variables of classes used before the class is declared | ||
* [bug] semicolon at end of file no longer triggers false positives for `whitespace` rule | ||
* [bug] hoisted functions no longer cause false positives for the `no-unreachable` rule | ||
* [bug] the rule loader no longer transforms/ignores the leading and trailing underscores and dashes of rule names in the config file | ||
* [bug] `export import` statements no longer false positives for `no-unused-variable-rule` | ||
* [docs] added documentation for creating custom rules and formatters | ||
* [docs] added sample `tslint.json` file, under `docs/sample.tslint.json` | ||
v0.4.6 | ||
@@ -5,0 +18,0 @@ --- |
{ | ||
"name": "tslint", | ||
"version": "0.4.6", | ||
"version": "0.4.7", | ||
"description": "a static analysis linter for TypeScript", | ||
@@ -5,0 +5,0 @@ "bin": { |
102
README.md
@@ -9,2 +9,4 @@ TSLint [![NPM version](https://badge.fury.io/js/tslint.png)](http://badge.fury.io/js/tslint) [![Builds](https://api.travis-ci.org/repositories/palantir/tslint.png?branch=master)](https://travis-ci.org/palantir/tslint) | ||
A sample configuration file with all options is available [here](https://github.com/palantir/tslint/blob/master/docs/sample.tslint.json). | ||
* `ban` bans the use of specific functions. Options are ["object", "function"] pairs that ban the use of object.function() | ||
@@ -41,2 +43,3 @@ * `class-name` enforces PascalCased class and interface names. | ||
* `no-trailing-whitespace` disallows trailing whitespace at the end of a line. | ||
* `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. | ||
@@ -93,2 +96,101 @@ * `"check-parameters"` disallows unused function and constructor parameters. | ||
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. | ||
Now, let us first write the rule in TypeScript. At the top, we reference TSLint's [definition](https://github.com/palantir/tslint/blob/master/lib/tslint.d.ts) file. The exported class name must always be named `Rule` and extend from `Lint.Rules.AbstractRule`. | ||
```javascript | ||
/// <reference path='tslint.d.ts' /> | ||
export class Rule extends Lint.Rules.AbstractRule { | ||
public static FAILURE_STRING = "import statement forbidden"; | ||
public apply(syntaxTree: TypeScript.SyntaxTree): Lint.RuleFailure[] { | ||
return this.applyWithWalker(new NoImportsWalker(syntaxTree, this.getOptions())); | ||
} | ||
} | ||
``` | ||
The walker takes care of all the work. | ||
```javascript | ||
class NoImportsWalker extends Lint.RuleWalker { | ||
public visitImportDeclaration(node: TypeScript.ImportDeclarationSyntax) { | ||
// get the current position and skip over any leading whitespace | ||
var position = this.position() + node.leadingTriviaWidth(); | ||
// create a failure at the current position | ||
this.addFailure(this.createFailure(position, node.width(), Rule.FAILURE_STRING)); | ||
// call the base version of this visitor to actually parse this node | ||
super.visitImportDeclaration(node); | ||
} | ||
} | ||
``` | ||
Given a walker, TypeScript's parser visits the AST using the visitor pattern. So the rule walkers only need to override the appropriate visitor methods to enforce its checks. For reference, the base walker can be found in [syntaxWalker.generated.ts](https://github.com/palantir/tslint/blob/master/src/typescript/src/compiler/syntax/syntaxWalker.generated.ts) within the TypeScript source code. | ||
We still need to hook up this new rule to TSLint. First make sure to compile `noImportsRule.ts`: `tsc -m commonjs 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. | ||
```javascript | ||
function Rule() { | ||
Lint.Rules.AbstractRule.apply(this, arguments); | ||
} | ||
Rule.prototype = Object.create(Lint.Rules.AbstractRule.prototype); | ||
Rule.prototype.apply = function(syntaxTree) { | ||
return this.applyWithWalker(new NoImportsWalker(syntaxTree, this.getOptions())); | ||
}; | ||
function NoImportsWalker() { | ||
Lint.RuleWalker.apply(this, arguments); | ||
} | ||
NoImportsWalker.prototype = Object.create(Lint.RuleWalker.prototype); | ||
NoImportsWalker.prototype.visitImportDeclaration = function (node) { | ||
// get the current position and skip over any leading whitespace | ||
var position = this.position() + node.leadingTriviaWidth(); | ||
// create a failure at the current position | ||
this.addFailure(this.createFailure(position, node.width(), "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. | ||
Finally, core rules cannot be overwritten with a custom implementation, and rules can also take in options (retrieved via `this.getOptions()`). | ||
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. | ||
```javascript | ||
/// <reference path='tslint.d.ts' /> | ||
export class Formatter extends Lint.Formatters.AbstractFormatter { | ||
public format(failures: Lint.RuleFailure[]): string { | ||
var failuresJSON: any[] = []; | ||
for (var i = 0; i < failures.length; ++i) { | ||
failuresJSON.push(failures[i].toJson()); | ||
} | ||
return JSON.stringify(failuresJSON); | ||
} | ||
} | ||
``` | ||
Such custom formatters can also be written in Javascript. Additionally, formatter files are always named with the suffix `Formatter`, and referenced from TSLint without its suffix. | ||
Installation | ||
@@ -95,0 +197,0 @@ ------------ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7055009
55
125708
300