diff --git a/.travis.yml b/.travis.yml index 81614ca7922..3e2435772bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,4 @@ language: node_js node_js: - "0.10" - "0.12" + - "4.1" diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f6a1f10a8..7cdd9bcd57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ Change Log === +v2.6.0-dev.2 +--- +* Upgrade TypeScript compiler to `v1.7.0-dev.20151003` +* [bugfix] `no-unused-expression` rule now handles yield expressions properly (#706) + v2.6.0-dev.1 --- * Upgrade TypeScript compiler to `v1.7.0-dev.20150924` +v2.5.1 +--- +* [new-rule] no-inferrable-types rule (#676) +* [new-rule-option] "avoid-escape" option for quotemark rule (#543) +* [bugfix] type declaration for tslint external module #686 +* [enhancement] `AbstractRule` and `AbstractFormatter` are now abstract classes (#631) + * Note: `Lint.abstract()` is now deprecated + v2.5.0 --- * Use TypeScript compiler `v1.6.2` diff --git a/README.md b/README.md index c383003abc1..2bb2db61ffa 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ A sample configuration file with all options is available [here](https://github. * `jsdoc-format` enforces basic format rules for jsdoc comments -- comments starting with `/**` * each line contains an asterisk and asterisks must be aligned * each asterisk must be followed by either a space or a newline (except for the first and the last) - * the only characters before the asterisk on each line must be whitepace characters + * the only characters before the asterisk on each line must be whitespace characters * one line comments must start with `/** ` and end with ` */` * `label-position` enforces labels only on sensible statements. * `label-undefined` checks that labels are defined before usage. @@ -170,6 +170,7 @@ A sample configuration file with all options is available [here](https://github. * `no-shadowed-variable` disallows shadowed variable declarations. * `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-string-literal` disallows object access via string literals. @@ -245,10 +246,11 @@ TSLint ships with a set of core rules that can be configured. However, users are 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`. +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`. ```typescript -/// +/// +/// export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "import statement forbidden"; @@ -280,7 +282,6 @@ We still need to hook up this new rule to TSLint. First make sure to compile `no Now, let us rewrite the same rule in Javascript. ```javascript - function Rule() { Lint.Rules.AbstractRule.apply(this, arguments); } @@ -314,8 +315,9 @@ 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 -/// +```typescript +/// +/// export class Formatter extends Lint.Formatters.AbstractFormatter { public format(failures: Lint.RuleFailure[]): string { diff --git a/appveyor.yml b/appveyor.yml index 4e0c1babefc..d11d9faab74 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,7 @@ environment: matrix: - nodejs_version: "0.10" - nodejs_version: "0.12" + - nodejs_version: "4.1" install: - ps: Install-Product node $env:nodejs_version diff --git a/docs/sample.tslint.json b/docs/sample.tslint.json index dfbd841cce2..68b935fbc31 100644 --- a/docs/sample.tslint.json +++ b/docs/sample.tslint.json @@ -44,6 +44,7 @@ "no-duplicate-variable": true, "no-empty": true, "no-eval": true, + "no-inferrable-types": true, "no-internal-module": true, "no-require-imports": true, "no-string-literal": true, diff --git a/lib/tslint.d.ts b/lib/tslint.d.ts index a4075eda50c..db1832ed825 100644 --- a/lib/tslint.d.ts +++ b/lib/tslint.d.ts @@ -92,10 +92,10 @@ declare module Lint { } } declare module Lint { - class ScopeAwareRuleWalker extends RuleWalker { + abstract class ScopeAwareRuleWalker extends RuleWalker { private scopeStack; constructor(sourceFile: ts.SourceFile, options?: any); - createScope(): T; + abstract createScope(): T; getCurrentScope(): T; getAllScopes(): T[]; getCurrentDepth(): number; @@ -106,8 +106,8 @@ declare module Lint { } } declare module Lint.Formatters { - class AbstractFormatter implements Lint.IFormatter { - format(failures: Lint.RuleFailure[]): string; + abstract class AbstractFormatter implements Lint.IFormatter { + abstract format(failures: Lint.RuleFailure[]): string; } } declare module Lint { @@ -120,12 +120,12 @@ declare module Lint { function createLanguageService(fileName: string, source: string): ts.LanguageService; } declare module Lint.Rules { - class AbstractRule implements Lint.IRule { + abstract class AbstractRule implements Lint.IRule { private value; private options; constructor(ruleName: string, value: any, disabledIntervals: Lint.IDisabledInterval[]); getOptions(): Lint.IOptions; - apply(sourceFile: ts.SourceFile): RuleFailure[]; + abstract apply(sourceFile: ts.SourceFile): RuleFailure[]; applyWithWalker(walker: Lint.RuleWalker): RuleFailure[]; isEnabled(): boolean; } @@ -189,10 +189,10 @@ declare module Lint { function isNodeFlagSet(node: ts.Node, flagToCheck: ts.NodeFlags): boolean; } declare module Lint { - class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalker { + abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalker { private blockScopeStack; constructor(sourceFile: ts.SourceFile, options?: any); - createBlockScope(): U; + abstract createBlockScope(): U; getCurrentBlockScope(): U; onBlockScopeStart(): void; getCurrentBlockDepth(): number; @@ -265,5 +265,6 @@ declare module Lint { } } declare module "tslint" { - export = Lint; + import Linter = Lint.Linter; + export = Linter; } diff --git a/package.json b/package.json index f7e5a2450ab..699400e7b57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "2.6.0-dev.1", + "version": "2.6.0-dev.2", "description": "a static analysis linter for TypeScript", "bin": { "tslint": "./bin/tslint" diff --git a/src/formatters/tsconfig.json b/src/formatters/tsconfig.json index 2d9ec7c9de3..4154dde2810 100644 --- a/src/formatters/tsconfig.json +++ b/src/formatters/tsconfig.json @@ -1,5 +1,5 @@ { - "version": "1.5.0-beta", + "version": "1.6.2", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, diff --git a/src/language/formatter/abstractFormatter.ts b/src/language/formatter/abstractFormatter.ts index cc5c65bf4d9..90f0fe02b48 100644 --- a/src/language/formatter/abstractFormatter.ts +++ b/src/language/formatter/abstractFormatter.ts @@ -15,9 +15,7 @@ */ module Lint.Formatters { - export class AbstractFormatter implements Lint.IFormatter { - public format(failures: Lint.RuleFailure[]): string { - throw Lint.abstract(); - } + export abstract class AbstractFormatter implements Lint.IFormatter { + public abstract format(failures: Lint.RuleFailure[]): string; } } diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index cfb9527679b..5c60a1bacf7 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -15,7 +15,7 @@ */ module Lint.Rules { - export class AbstractRule implements Lint.IRule { + export abstract class AbstractRule implements Lint.IRule { private value: any; private options: Lint.IOptions; @@ -38,11 +38,7 @@ module Lint.Rules { return this.options; } - /* tslint:disable:no-unused-variable */ - public apply(sourceFile: ts.SourceFile): RuleFailure[] { - throw Lint.abstract(); - } - /* tslint:enable:no-unused-variable */ + public abstract apply(sourceFile: ts.SourceFile): RuleFailure[]; public applyWithWalker(walker: Lint.RuleWalker): RuleFailure[] { walker.walk(walker.getSourceFile()); diff --git a/src/language/utils.ts b/src/language/utils.ts index fd074604ca4..d34176dd326 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -58,6 +58,7 @@ module Lint { } export function abstract() { + console.warn("Lint.abstract() is deprecated and will be removed in a future release. TSLint now uses abstract classes."); return "abstract method not implemented"; } diff --git a/src/language/walker/blockScopeAwareRuleWalker.ts b/src/language/walker/blockScopeAwareRuleWalker.ts index 688a7ae2ed1..4f44a7d080f 100644 --- a/src/language/walker/blockScopeAwareRuleWalker.ts +++ b/src/language/walker/blockScopeAwareRuleWalker.ts @@ -19,7 +19,7 @@ module Lint { * An AST walker that is aware of block scopes in addition to regular scopes. Block scopes * are a superset of regular scopes (new block scopes are created more frequently in a program). */ - export class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalker { + export abstract class BlockScopeAwareRuleWalker extends ScopeAwareRuleWalker { private blockScopeStack: U[]; constructor(sourceFile: ts.SourceFile, options?: any) { @@ -29,9 +29,7 @@ module Lint { this.blockScopeStack = [this.createBlockScope()]; } - public createBlockScope(): U { - throw Lint.abstract(); - } + public abstract createBlockScope(): U; public getCurrentBlockScope(): U { return this.blockScopeStack[this.blockScopeStack.length - 1]; diff --git a/src/language/walker/scopeAwareRuleWalker.ts b/src/language/walker/scopeAwareRuleWalker.ts index a544e5bef5f..fe44aa0e152 100644 --- a/src/language/walker/scopeAwareRuleWalker.ts +++ b/src/language/walker/scopeAwareRuleWalker.ts @@ -15,7 +15,7 @@ */ module Lint { - export class ScopeAwareRuleWalker extends RuleWalker { + export abstract class ScopeAwareRuleWalker extends RuleWalker { private scopeStack: T[]; constructor(sourceFile: ts.SourceFile, options?: any) { @@ -25,9 +25,7 @@ module Lint { this.scopeStack = [this.createScope()]; } - public createScope(): T { - throw Lint.abstract(); - } + public abstract createScope(): T; public getCurrentScope(): T { return this.scopeStack[this.scopeStack.length - 1]; diff --git a/src/rules/noInferrableTypesRule.ts b/src/rules/noInferrableTypesRule.ts new file mode 100644 index 00000000000..3f960ce3b23 --- /dev/null +++ b/src/rules/noInferrableTypesRule.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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. + */ + +export class Rule extends Lint.Rules.AbstractRule { + public static FAILURE_STRING_FACTORY = (type: string) => `LHS type (${type}) inferred by RHS expression, remove type annotation`; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new InferrableTypeWalker(sourceFile, this.getOptions())); + } +} + +class InferrableTypeWalker extends Lint.RuleWalker { + public visitVariableDeclaration(node: ts.VariableDeclaration) { + this.checkDeclaration(node); + super.visitVariableDeclaration(node); + } + + public visitParameterDeclaration(node: ts.ParameterDeclaration) { + this.checkDeclaration(node); + super.visitParameterDeclaration(node); + } + + private checkDeclaration(node: ts.ParameterDeclaration | ts.VariableDeclaration) { + if (node.type != null && node.initializer != null) { + let failure: string; + switch (node.type.kind) { + case ts.SyntaxKind.BooleanKeyword: + if (node.initializer.kind === ts.SyntaxKind.TrueKeyword || node.initializer.kind === ts.SyntaxKind.FalseKeyword) { + failure = "boolean"; + } + break; + case ts.SyntaxKind.NumberKeyword: + if (node.initializer.kind === ts.SyntaxKind.NumericLiteral) { + failure = "number"; + } + break; + case ts.SyntaxKind.StringKeyword: + switch (node.initializer.kind) { + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + case ts.SyntaxKind.TemplateExpression: + failure = "string"; + break; + default: + break; + } + break; + } + if (failure) { + this.addFailure(this.createFailure(node.type.getStart(), node.type.getWidth(), Rule.FAILURE_STRING_FACTORY(failure))); + } + } + } +} diff --git a/src/rules/noUnusedExpressionRule.ts b/src/rules/noUnusedExpressionRule.ts index f659b3b5e54..018286e3595 100644 --- a/src/rules/noUnusedExpressionRule.ts +++ b/src/rules/noUnusedExpressionRule.ts @@ -40,7 +40,7 @@ class UnusedExpressionWalker extends Lint.RuleWalker { if (expressionText === "\"use strict\"" || expressionText === "'use strict'") { return; } - } else if (node.expression.kind === ts.SyntaxKind.DeleteExpression) { + } else if (node.expression.kind === ts.SyntaxKind.DeleteExpression || node.expression.kind === ts.SyntaxKind.YieldExpression) { return; } diff --git a/src/rules/tsconfig.json b/src/rules/tsconfig.json index c675b0e6959..9fadf1a3a39 100644 --- a/src/rules/tsconfig.json +++ b/src/rules/tsconfig.json @@ -1,5 +1,5 @@ { - "version": "1.5.0-beta", + "version": "1.6.2", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, @@ -44,6 +44,7 @@ "./noDuplicateVariableRule.ts", "./noEmptyRule.ts", "./noEvalRule.ts", + "./noInferrableTypesRule.ts", "./noInternalModuleRule.ts", "./noRequireImportsRule.ts", "./noShadowedVariableRule.ts", diff --git a/src/tsconfig.json b/src/tsconfig.json index 70bf865be4e..eb1ebf07bf2 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,5 +1,5 @@ { - "version": "1.5.0-beta", + "version": "1.6.2", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, diff --git a/src/tslint.ts b/src/tslint.ts index 65870b07481..babf2ceb0b4 100644 --- a/src/tslint.ts +++ b/src/tslint.ts @@ -33,7 +33,7 @@ module Lint { } export class Linter { - public static VERSION = "2.6.0-dev.1"; + public static VERSION = "2.6.0-dev.2"; private fileName: string; private source: string; @@ -114,5 +114,6 @@ module.exports = Lint.Linter; module.exports.findConfiguration = Lint.Configuration.findConfiguration; declare module "tslint" { - export = Lint; + import Linter = Lint.Linter; + export = Linter; } diff --git a/test/files/rules/noinferrabletypes.test.ts b/test/files/rules/noinferrabletypes.test.ts new file mode 100644 index 00000000000..27c40f2aca3 --- /dev/null +++ b/test/files/rules/noinferrabletypes.test.ts @@ -0,0 +1,21 @@ +// errors, inferrable type is declared +let x: number = 7; +let y: boolean = false; +let z: string = "foo"; + +// errors, types are inferrable +function foo (a: number = 5, b: boolean = true, c: string = "bah") { } + +// not errors, inferrable type is not declared +let _x = 7; +let _y = false; +let _z = "foo"; + +// not error, type is not inferrable +let weird: any = 123; + +// not errors, inferrable type is not declared +function bar(a = 5, b = true, c = "bah") { } + +// not errors, types are not inferrable +function baz(a: any = 5, b: any = true, c: any = "bah") { } diff --git a/test/files/rules/unused.expression.test.ts b/test/files/rules/unused.expression.test.ts index e42474319e3..87f87bf73ad 100644 --- a/test/files/rules/unused.expression.test.ts +++ b/test/files/rules/unused.expression.test.ts @@ -48,3 +48,8 @@ a => fun2(a); var obj = {}; delete obj.key; +function* g(): Iterable { + for (let i = 0; i < 100; i++) { + yield i; + } +} diff --git a/test/rules/noInferrableTypesRuleTests.ts b/test/rules/noInferrableTypesRuleTests.ts new file mode 100644 index 00000000000..6bd7a363d12 --- /dev/null +++ b/test/rules/noInferrableTypesRuleTests.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2015 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. + */ + +describe("", () => { + const Rule = Lint.Test.getRule("no-inferrable-types"); + const fileName = "rules/noinferrabletypes.test.ts"; + + it("forbids explicit type declarations where easily inferrable", () => { + const createFailure = (start: number[], end: number[], type: string) => { + const failureString = Rule.FAILURE_STRING_FACTORY(type); + return Lint.Test.createFailure(fileName, start, end, failureString); + }; + + const expectedFailures = [ + createFailure([2, 8], [2, 14], "number"), + createFailure([3, 8], [3, 15], "boolean"), + createFailure([4, 8], [4, 14], "string"), + createFailure([7, 18], [7, 24], "number"), + createFailure([7, 33], [7, 40], "boolean"), + createFailure([7, 52], [7, 58], "string") + ]; + const actualFailures = Lint.Test.applyRuleOnFile(fileName, Rule); + Lint.Test.assertFailuresEqual(actualFailures, expectedFailures); + }); +}); diff --git a/test/rules/noInternalModuleTests.ts b/test/rules/noInternalModuleRuleTests.ts similarity index 100% rename from test/rules/noInternalModuleTests.ts rename to test/rules/noInternalModuleRuleTests.ts diff --git a/test/rules/noRequireImportsTests.ts b/test/rules/noRequireImportsRuleTests.ts similarity index 100% rename from test/rules/noRequireImportsTests.ts rename to test/rules/noRequireImportsRuleTests.ts diff --git a/test/tsconfig.json b/test/tsconfig.json index 236ad58fe37..5885599c073 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,5 +1,5 @@ { - "version": "1.5.0-beta", + "version": "1.6.2", "compilerOptions": { "module": "commonjs", "noImplicitAny": true, @@ -63,8 +63,9 @@ "./rules/noDuplicateVariableRuleTests.ts", "./rules/noEmptyRuleTests.ts", "./rules/noEvalRuleTests.ts", - "./rules/noInternalModuleTests.ts", - "./rules/noRequireImportsTests.ts", + "./rules/noInferrableTypesRuleTests.ts", + "./rules/noInternalModuleRuleTests.ts", + "./rules/noRequireImportsRuleTests.ts", "./rules/noShadowedVariableRuleTests.ts", "./rules/noStringLiteralRuleTests.ts", "./rules/noSwitchCaseFallThroughRuleTests.ts", diff --git a/tslint.json b/tslint.json index bc8c2aefc91..51e17998a1c 100644 --- a/tslint.json +++ b/tslint.json @@ -29,6 +29,7 @@ "no-duplicate-variable": true, "no-empty": true, "no-eval": true, + "no-inferrable-types": true, "no-shadowed-variable": true, "no-string-literal": true, "no-switch-case-fall-through": true, diff --git a/typings/typescriptServices.d.ts b/typings/typescriptServices.d.ts index 761ce39d418..bfa90711aef 100644 --- a/typings/typescriptServices.d.ts +++ b/typings/typescriptServices.d.ts @@ -343,6 +343,7 @@ declare namespace ts { OctalLiteral = 65536, Namespace = 131072, ExportContext = 262144, + ContainsThis = 524288, Modifier = 2035, AccessibilityModifier = 112, BlockScoped = 49152, @@ -1082,6 +1083,7 @@ declare namespace ts { decreaseIndent(): void; clear(): void; trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError(): void; } const enum TypeFormatFlags { None = 0, @@ -1202,6 +1204,7 @@ declare namespace ts { Instantiated = 131072, ObjectLiteral = 524288, ESSymbol = 16777216, + ThisType = 33554432, StringLike = 258, NumberLike = 132, ObjectType = 80896, @@ -1223,6 +1226,7 @@ declare namespace ts { typeParameters: TypeParameter[]; outerTypeParameters: TypeParameter[]; localTypeParameters: TypeParameter[]; + thisType: TypeParameter; } interface InterfaceTypeWithDeclaredMembers extends InterfaceType { declaredProperties: Symbol[]; @@ -1348,6 +1352,7 @@ declare namespace ts { AMD = 2, UMD = 3, System = 4, + ES6 = 5, } const enum JsxEmit { None = 0, @@ -1417,7 +1422,7 @@ declare namespace ts { write(s: string): void; readFile(path: string, encoding?: string): string; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - watchFile?(path: string, callback: (path: string) => void): FileWatcher; + watchFile?(path: string, callback: (path: string, removed: boolean) => void): FileWatcher; resolvePath(path: string): string; fileExists(path: string): boolean; directoryExists(path: string): boolean;