diff --git a/CHANGELOG.md b/CHANGELOG.md index b730f887261..af5bde5d233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Change Log === +v3.14.0-dev.0 +--- +* [enhancement] Add optional type information to rules (#1323) + +Thanks to our contributors! +* @ScottSWu + +v3.13.0 +--- +* Stable release containing changes from the last dev release (v3.13.0-dev.0) + v3.13.0-dev.0 --- * [new-rule] `ordered-imports` rule (#1325) diff --git a/README.md b/README.md index c4bb6b2a2d8..c1514a93528 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ Options: -e, --exclude exclude globs from path expansion -t, --format output format (prose, json, verbose, pmd, msbuild, checkstyle) [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 ``` @@ -182,6 +184,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. @@ -214,6 +224,23 @@ const linter = new Linter(fileName, fileContents, options); const result = linter.lint(); ``` +#### Type Checking + +To enable rules that work with the type checker, a TypeScript program object must be passed to the linter when using the programmatic API. Helper functions are provided to create a program from a `tsconfig.json` file. A project directory can be specified if project files do not lie in the same directory as the `tsconfig.json` file. + +```javascript +const program = Linter.createProgram("tsconfig.json", "projectDir/"); +const files = Linter.getFileNames(program); +const results = files.map(file => { + const fileContents = program.getSourceFile(file).getFullText(); + const linter = new Linter(file, fileContents, options, program); + return result.lint(); +}); +``` + +When using the CLI, the `--project` flag will automatically create a program from the specified `tsconfig.json` file. Adding `--type-check` then enables rules that require the type checker. + + Core Rules ----- [back to ToC ↑](#table-of-contents) @@ -313,6 +340,7 @@ Core rules are included in the `tslint` package. * `"jsx-double"` enforces double quotes for JSX attributes. * `"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`. +* `restrict-plus-operands` enforces the type of addition operands to be both `string` or both `number` (requires type checking). * `semicolon` enforces consistent semicolon usage at the end of every statement. Rule options: * `"always"` enforces semicolons at the end of every statement. * `"never"` disallows semicolons at the end of every statement except for when they are necessary. diff --git a/package.json b/package.json index d8d8fd98c9c..21ca3fc1354 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tslint", - "version": "3.13.0-dev.0", + "version": "3.14.0-dev.0", "description": "An extensible static analysis linter for the TypeScript language", "bin": { "tslint": "./bin/tslint" @@ -43,7 +43,7 @@ "mocha": "^2.2.5", "tslint": "next", "tslint-test-config-non-relative": "file:test/external/tslint-test-config-non-relative", - "typescript": "next" + "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" diff --git a/scripts/buildDocs.ts b/scripts/buildDocs.ts index c007f7dd121..79ef1c6c0bd 100644 --- a/scripts/buildDocs.ts +++ b/scripts/buildDocs.ts @@ -33,10 +33,11 @@ import * as fs from "fs"; import * as glob from "glob"; -import * as path from "path"; import * as yaml from "js-yaml"; -import {IRuleMetadata} from "../lib/language/rule/rule"; +import * as path from "path"; + import {AbstractRule} from "../lib/language/rule/abstractRule"; +import {IRuleMetadata} from "../lib/language/rule/rule"; const DOCS_DIR = "../../tslint-gh-pages"; const DOCS_RULE_DIR = path.join(DOCS_DIR, "rules"); diff --git a/src/configuration.ts b/src/configuration.ts index ce8c648cb79..ee1e98d0eff 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -15,9 +15,9 @@ * limitations under the License. */ +import findup = require("findup-sync"); import * as fs from "fs"; import * as path from "path"; -import * as findup from "findup-sync"; import * as resolve from "resolve"; import {arrayify, objectify, stripComments} from "./utils"; diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index 8f32cb7e0d4..a01657499b3 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {scanAllTokens} from "./language/utils"; import {SkippableTokenAwareRuleWalker} from "./language/walker/skippableTokenAwareRuleWalker"; import {IEnableDisablePosition} from "./ruleLoader"; @@ -64,10 +65,23 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { const isEnabled = enableOrDisableMatch[1] === "enable"; const isCurrentLine = enableOrDisableMatch[3] === "line"; const isNextLine = enableOrDisableMatch[3] === "next-line"; + let rulesList = ["all"]; - if (commentTextParts.length > 2) { + + if (commentTextParts.length === 2) { + // an implicit whitespace separator is used for the rules list. + rulesList = commentTextParts[1].split(/\s+/).slice(1); + + // remove empty items and potential comment end. + rulesList = rulesList.filter(item => !!item && item.indexOf("*/") === -1); + + // potentially there were no items, so default to `all`. + rulesList = rulesList.length > 0 ? rulesList : ["all"]; + } else if (commentTextParts.length > 2) { + // an explicit separator was specified for the rules list. rulesList = commentTextParts[2].split(/\s+/); } + for (const ruleToAdd of rulesList) { if (!(ruleToAdd in this.enableDisableRuleMap)) { this.enableDisableRuleMap[ruleToAdd] = []; diff --git a/src/language/languageServiceHost.ts b/src/language/languageServiceHost.ts index 40ffc125464..180e110c054 100644 --- a/src/language/languageServiceHost.ts +++ b/src/language/languageServiceHost.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {createCompilerOptions} from "./utils"; export function createLanguageServiceHost(fileName: string, source: string): ts.LanguageServiceHost { diff --git a/src/language/rule/abstractRule.ts b/src/language/rule/abstractRule.ts index 1c590d35fed..4e5af465c71 100644 --- a/src/language/rule/abstractRule.ts +++ b/src/language/rule/abstractRule.ts @@ -16,23 +16,22 @@ */ import * as ts from "typescript"; + import {IOptions} from "../../lint"; import {RuleWalker} from "../walker/ruleWalker"; -import {IRule, IRuleMetadata, IDisabledInterval, RuleFailure} from "./rule"; +import {IDisabledInterval, IRule, IRuleMetadata, RuleFailure} from "./rule"; export abstract class AbstractRule implements IRule { public static metadata: IRuleMetadata; - private value: any; private options: IOptions; - constructor(ruleName: string, value: any, disabledIntervals: IDisabledInterval[]) { + constructor(ruleName: string, private value: any, disabledIntervals: IDisabledInterval[]) { let ruleArguments: any[] = []; if (Array.isArray(value) && value.length > 1) { ruleArguments = value.slice(1); } - this.value = value; this.options = { disabledIntervals: disabledIntervals, ruleArguments: ruleArguments, diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 11debf0e676..59364165987 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {RuleWalker} from "../walker/ruleWalker"; export interface IRuleMetadata { @@ -61,6 +62,11 @@ export interface IRuleMetadata { * An explanation of why the rule is useful. */ rationale?: string; + + /** + * Whether or not the rule requires type info to run. + */ + requiresTypeInfo?: boolean; } export type RuleType = "functionality" | "maintainability" | "style" | "typescript"; @@ -84,12 +90,7 @@ export interface IRule { } export class RuleFailurePosition { - private position: number; - private lineAndCharacter: ts.LineAndCharacter; - - constructor(position: number, lineAndCharacter: ts.LineAndCharacter) { - this.position = position; - this.lineAndCharacter = lineAndCharacter; + constructor(private position: number, private lineAndCharacter: ts.LineAndCharacter) { } public getPosition() { @@ -119,25 +120,19 @@ export class RuleFailurePosition { } export class RuleFailure { - private sourceFile: ts.SourceFile; private fileName: string; private startPosition: RuleFailurePosition; private endPosition: RuleFailurePosition; - private failure: string; - private ruleName: string; - constructor(sourceFile: ts.SourceFile, + constructor(private sourceFile: ts.SourceFile, start: number, end: number, - failure: string, - ruleName: string) { + private failure: string, + private ruleName: string) { - this.sourceFile = sourceFile; this.fileName = sourceFile.fileName; this.startPosition = this.createFailurePosition(start); this.endPosition = this.createFailurePosition(end); - this.failure = failure; - this.ruleName = ruleName; } public getFileName() { diff --git a/src/language/rule/typedRule.ts b/src/language/rule/typedRule.ts new file mode 100644 index 00000000000..05b5797bb45 --- /dev/null +++ b/src/language/rule/typedRule.ts @@ -0,0 +1,30 @@ +/** + * @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 ts from "typescript"; + +import {AbstractRule} from "./abstractRule"; +import {RuleFailure} from "./rule"; + +export abstract class TypedRule extends AbstractRule { + 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`); + } + + public abstract applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[]; +} diff --git a/src/language/utils.ts b/src/language/utils.ts index 4559ad28b71..3891961885c 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -17,6 +17,7 @@ import * as path from "path"; import * as ts from "typescript"; + import {IDisabledInterval, RuleFailure} from "./rule/rule"; export function getSourceFile(fileName: string, source: string): ts.SourceFile { diff --git a/src/language/walker/blockScopeAwareRuleWalker.ts b/src/language/walker/blockScopeAwareRuleWalker.ts index 6bfd13d6215..e1e17afea24 100644 --- a/src/language/walker/blockScopeAwareRuleWalker.ts +++ b/src/language/walker/blockScopeAwareRuleWalker.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {ScopeAwareRuleWalker} from "./scopeAwareRuleWalker"; /** diff --git a/src/language/walker/index.ts b/src/language/walker/index.ts index 9d0819b9900..b762aa289ed 100644 --- a/src/language/walker/index.ts +++ b/src/language/walker/index.ts @@ -16,6 +16,7 @@ */ export * from "./blockScopeAwareRuleWalker"; +export * from "./programAwareRuleWalker"; export * from "./ruleWalker"; export * from "./scopeAwareRuleWalker"; export * from "./skippableTokenAwareRuleWalker"; diff --git a/src/language/walker/programAwareRuleWalker.ts b/src/language/walker/programAwareRuleWalker.ts new file mode 100644 index 00000000000..6b29e1fdcd4 --- /dev/null +++ b/src/language/walker/programAwareRuleWalker.ts @@ -0,0 +1,39 @@ +/** + * @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 ts from "typescript"; + +import {IOptions} from "../../lint"; +import {RuleWalker} from "./ruleWalker"; + +export class ProgramAwareRuleWalker extends RuleWalker { + private typeChecker: ts.TypeChecker; + + constructor(sourceFile: ts.SourceFile, options: IOptions, private program: ts.Program) { + super(sourceFile, options); + + this.typeChecker = program.getTypeChecker(); + } + + public getProgram(): ts.Program { + return this.program; + } + + public getTypeChecker(): ts.TypeChecker { + return this.typeChecker; + } +} diff --git a/src/language/walker/ruleWalker.ts b/src/language/walker/ruleWalker.ts index b74950545f8..25edcb97431 100644 --- a/src/language/walker/ruleWalker.ts +++ b/src/language/walker/ruleWalker.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {IOptions} from "../../lint"; import {IDisabledInterval, RuleFailure} from "../rule/rule"; import {doesIntersect} from "../utils"; @@ -26,17 +27,15 @@ export class RuleWalker extends SyntaxWalker { private position: number; private options: any[]; private failures: RuleFailure[]; - private sourceFile: ts.SourceFile; private disabledIntervals: IDisabledInterval[]; private ruleName: string; - constructor(sourceFile: ts.SourceFile, options: IOptions) { + constructor(private sourceFile: ts.SourceFile, options: IOptions) { super(); this.position = 0; this.failures = []; this.options = options.ruleArguments; - this.sourceFile = sourceFile; this.limit = this.sourceFile.getFullWidth(); this.disabledIntervals = options.disabledIntervals; this.ruleName = options.ruleName; diff --git a/src/language/walker/scopeAwareRuleWalker.ts b/src/language/walker/scopeAwareRuleWalker.ts index 7101ea57742..0c2b988592d 100644 --- a/src/language/walker/scopeAwareRuleWalker.ts +++ b/src/language/walker/scopeAwareRuleWalker.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {RuleWalker} from "./ruleWalker"; export abstract class ScopeAwareRuleWalker extends RuleWalker { diff --git a/src/language/walker/skippableTokenAwareRuleWalker.ts b/src/language/walker/skippableTokenAwareRuleWalker.ts index c2af57f841a..a374fad746d 100644 --- a/src/language/walker/skippableTokenAwareRuleWalker.ts +++ b/src/language/walker/skippableTokenAwareRuleWalker.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import {IOptions} from "../../lint"; import {RuleWalker} from "./ruleWalker"; diff --git a/src/lint.ts b/src/lint.ts index 263660cc139..a154c2f6018 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -17,11 +17,11 @@ import * as configuration from "./configuration"; import * as formatters from "./formatters"; -import * as linter from "./tslint"; +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 {RuleFailure} from "./language/rule/rule"; export * from "./language/rule/rule"; export * from "./enableDisableRules"; diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index b2af511f91d..5b47c0728fe 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -18,8 +18,9 @@ import * as fs from "fs"; import * as path from "path"; import {camelize} from "underscore.string"; + import {getRulesDirectories} from "./configuration"; -import {IRule, IDisabledInterval} from "./language/rule/rule"; +import {IDisabledInterval, IRule} from "./language/rule/rule"; const moduleDirectory = path.dirname(module.filename); const CORE_RULES_DIRECTORY = path.resolve(moduleDirectory, ".", "rules"); diff --git a/src/rules.ts b/src/rules.ts index 8c0cde4f290..1cafb4033db 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -16,3 +16,4 @@ */ export * from "./language/rule/abstractRule"; +export * from "./language/rule/typedRule"; diff --git a/src/rules/alignRule.ts b/src/rules/alignRule.ts index f58405c82e8..40327a20731 100644 --- a/src/rules/alignRule.ts +++ b/src/rules/alignRule.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import * as Lint from "../lint"; 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 = { diff --git a/src/rules/banRule.ts b/src/rules/banRule.ts index 2fb5dca9cb9..ec2c6b970b7 100644 --- a/src/rules/banRule.ts +++ b/src/rules/banRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/classNameRule.ts b/src/rules/classNameRule.ts index f14d6f50e3b..fb31837c796 100644 --- a/src/rules/classNameRule.ts +++ b/src/rules/classNameRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index 13b38f8f59d..3742141e7f5 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_SPACE = "check-space"; diff --git a/src/rules/curlyRule.ts b/src/rules/curlyRule.ts index bbde3d4a594..dc8cb3560e6 100644 --- a/src/rules/curlyRule.ts +++ b/src/rules/curlyRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/eoflineRule.ts b/src/rules/eoflineRule.ts index 58d806fbda2..c835290abb5 100644 --- a/src/rules/eoflineRule.ts +++ b/src/rules/eoflineRule.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import * as Lint from "../lint"; 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 = { diff --git a/src/rules/forinRule.ts b/src/rules/forinRule.ts index c5758194ec6..7c6f6e5f2a4 100644 --- a/src/rules/forinRule.ts +++ b/src/rules/forinRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/indentRule.ts b/src/rules/indentRule.ts index cbb2cd47da6..0cda08317de 100644 --- a/src/rules/indentRule.ts +++ b/src/rules/indentRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_USE_TABS = "tabs"; diff --git a/src/rules/interfaceNameRule.ts b/src/rules/interfaceNameRule.ts index c9434ce5de9..c2ac0f54145 100644 --- a/src/rules/interfaceNameRule.ts +++ b/src/rules/interfaceNameRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_ALWAYS = "always-prefix"; diff --git a/src/rules/jsdocFormatRule.ts b/src/rules/jsdocFormatRule.ts index 8d9773e056c..460a612e618 100644 --- a/src/rules/jsdocFormatRule.ts +++ b/src/rules/jsdocFormatRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/labelPositionRule.ts b/src/rules/labelPositionRule.ts index 8f38a845d42..e55474110fd 100644 --- a/src/rules/labelPositionRule.ts +++ b/src/rules/labelPositionRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/labelUndefinedRule.ts b/src/rules/labelUndefinedRule.ts index cc5cbb531e9..54df05d9c33 100644 --- a/src/rules/labelUndefinedRule.ts +++ b/src/rules/labelUndefinedRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/linebreakStyleRule.ts b/src/rules/linebreakStyleRule.ts index d45cf2f39bd..4fcc62ec381 100644 --- a/src/rules/linebreakStyleRule.ts +++ b/src/rules/linebreakStyleRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_LINEBREAK_STYLE_CRLF = "CRLF"; diff --git a/src/rules/maxLineLengthRule.ts b/src/rules/maxLineLengthRule.ts index 36d99fa0acd..2fa48b520e8 100644 --- a/src/rules/maxLineLengthRule.ts +++ b/src/rules/maxLineLengthRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/memberAccessRule.ts b/src/rules/memberAccessRule.ts index c1bd92272a9..61bb205ae23 100644 --- a/src/rules/memberAccessRule.ts +++ b/src/rules/memberAccessRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { @@ -57,10 +58,6 @@ export class Rule extends Lint.Rules.AbstractRule { } export class MemberAccessWalker extends Lint.RuleWalker { - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { - 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 diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index c663439a3e1..886c5486636 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -14,9 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as Lint from "../lint"; import * as ts from "typescript"; +import * as Lint from "../lint"; + /* start old options */ const OPTION_VARIABLES_BEFORE_FUNCTIONS = "variables-before-functions"; const OPTION_STATIC_BEFORE_INSTANCE = "static-before-instance"; diff --git a/src/rules/newParensRule.ts b/src/rules/newParensRule.ts index d06c37684a0..a39cb0eda60 100644 --- a/src/rules/newParensRule.ts +++ b/src/rules/newParensRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noAngleBracketTypeAssertionRule.ts b/src/rules/noAngleBracketTypeAssertionRule.ts index 156b9422d70..854467336c7 100644 --- a/src/rules/noAngleBracketTypeAssertionRule.ts +++ b/src/rules/noAngleBracketTypeAssertionRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noAnyRule.ts b/src/rules/noAnyRule.ts index b2576d0a31f..a0b602dc707 100644 --- a/src/rules/noAnyRule.ts +++ b/src/rules/noAnyRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noArgRule.ts b/src/rules/noArgRule.ts index 9ba40637956..8b34f8437ef 100644 --- a/src/rules/noArgRule.ts +++ b/src/rules/noArgRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noBitwiseRule.ts b/src/rules/noBitwiseRule.ts index 4f64e916fbd..4a5b0a74795 100644 --- a/src/rules/noBitwiseRule.ts +++ b/src/rules/noBitwiseRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noConditionalAssignmentRule.ts b/src/rules/noConditionalAssignmentRule.ts index f918f83cdb2..b59d87fed40 100644 --- a/src/rules/noConditionalAssignmentRule.ts +++ b/src/rules/noConditionalAssignmentRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noConsecutiveBlankLinesRule.ts b/src/rules/noConsecutiveBlankLinesRule.ts index c003fbbff63..69e737fd0f9 100644 --- a/src/rules/noConsecutiveBlankLinesRule.ts +++ b/src/rules/noConsecutiveBlankLinesRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noConsoleRule.ts b/src/rules/noConsoleRule.ts index 27c06d4fbf3..8c9f83fb52c 100644 --- a/src/rules/noConsoleRule.ts +++ b/src/rules/noConsoleRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; import * as BanRule from "./banRule"; diff --git a/src/rules/noConstructRule.ts b/src/rules/noConstructRule.ts index 389eadc96ec..b9fc89a19fa 100644 --- a/src/rules/noConstructRule.ts +++ b/src/rules/noConstructRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noConstructorVarsRule.ts b/src/rules/noConstructorVarsRule.ts index 7cbe5ff3eec..446bb1e1138 100644 --- a/src/rules/noConstructorVarsRule.ts +++ b/src/rules/noConstructorVarsRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noDebuggerRule.ts b/src/rules/noDebuggerRule.ts index 21c0de2f56c..a4b5a5130ba 100644 --- a/src/rules/noDebuggerRule.ts +++ b/src/rules/noDebuggerRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noDefaultExportRule.ts b/src/rules/noDefaultExportRule.ts index ee6c592d703..be65e9273cb 100644 --- a/src/rules/noDefaultExportRule.ts +++ b/src/rules/noDefaultExportRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noDuplicateKeyRule.ts b/src/rules/noDuplicateKeyRule.ts index 22f13d4c61a..9f8973f72ab 100644 --- a/src/rules/noDuplicateKeyRule.ts +++ b/src/rules/noDuplicateKeyRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noDuplicateVariableRule.ts b/src/rules/noDuplicateVariableRule.ts index 179878fa919..17c7694d625 100644 --- a/src/rules/noDuplicateVariableRule.ts +++ b/src/rules/noDuplicateVariableRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noEmptyRule.ts b/src/rules/noEmptyRule.ts index 5ffb361a47e..d3a3e20e536 100644 --- a/src/rules/noEmptyRule.ts +++ b/src/rules/noEmptyRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noEvalRule.ts b/src/rules/noEvalRule.ts index 63c0cc0739f..9e6ee898efb 100644 --- a/src/rules/noEvalRule.ts +++ b/src/rules/noEvalRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noInferrableTypesRule.ts b/src/rules/noInferrableTypesRule.ts index acf787ef004..99ee2f28f9f 100644 --- a/src/rules/noInferrableTypesRule.ts +++ b/src/rules/noInferrableTypesRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_IGNORE_PARMS = "ignore-params"; diff --git a/src/rules/noInternalModuleRule.ts b/src/rules/noInternalModuleRule.ts index 599b1dc0191..4e890d05ded 100644 --- a/src/rules/noInternalModuleRule.ts +++ b/src/rules/noInternalModuleRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noInvalidThisRule.ts b/src/rules/noInvalidThisRule.ts index 6f4f35bcab4..21511135a96 100644 --- a/src/rules/noInvalidThisRule.ts +++ b/src/rules/noInvalidThisRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; interface Scope { diff --git a/src/rules/noMergeableNamespaceRule.ts b/src/rules/noMergeableNamespaceRule.ts index c5c1dda6bce..d3466c5e286 100644 --- a/src/rules/noMergeableNamespaceRule.ts +++ b/src/rules/noMergeableNamespaceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { @@ -42,11 +43,8 @@ export class Rule extends Lint.Rules.AbstractRule { } class NoMergeableNamespaceWalker extends Lint.RuleWalker { - private languageService: ts.LanguageService; - - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, languageService: ts.LanguageService) { + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { super(sourceFile, options); - this.languageService = languageService; } public visitModuleDeclaration(node: ts.ModuleDeclaration) { diff --git a/src/rules/noNamespaceRule.ts b/src/rules/noNamespaceRule.ts index 171f12d3b78..980fd1adc47 100644 --- a/src/rules/noNamespaceRule.ts +++ b/src/rules/noNamespaceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noNullKeywordRule.ts b/src/rules/noNullKeywordRule.ts index 1a20fd6aed0..6a03a5ce244 100644 --- a/src/rules/noNullKeywordRule.ts +++ b/src/rules/noNullKeywordRule.ts @@ -18,6 +18,7 @@ // with due reference to https://github.com/Microsoft/TypeScript/blob/7813121c4d77e50aad0eed3152ef1f1156c7b574/scripts/tslint/noNullRule.ts import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noReferenceRule.ts b/src/rules/noReferenceRule.ts index cfb00c7cc4f..fcac6e8d74d 100644 --- a/src/rules/noReferenceRule.ts +++ b/src/rules/noReferenceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noRequireImportsRule.ts b/src/rules/noRequireImportsRule.ts index 8f5e545ce1c..f5aac5109e8 100644 --- a/src/rules/noRequireImportsRule.ts +++ b/src/rules/noRequireImportsRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noShadowedVariableRule.ts b/src/rules/noShadowedVariableRule.ts index 91f06ba94be..d4c5914966a 100644 --- a/src/rules/noShadowedVariableRule.ts +++ b/src/rules/noShadowedVariableRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noStringLiteralRule.ts b/src/rules/noStringLiteralRule.ts index 50b96d88486..e101df69f16 100644 --- a/src/rules/noStringLiteralRule.ts +++ b/src/rules/noStringLiteralRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noSwitchCaseFallThroughRule.ts b/src/rules/noSwitchCaseFallThroughRule.ts index aa436b98de6..afae05f0b48 100644 --- a/src/rules/noSwitchCaseFallThroughRule.ts +++ b/src/rules/noSwitchCaseFallThroughRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index ad42f71ba34..8998a5a37dc 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noUnreachableRule.ts b/src/rules/noUnreachableRule.ts index 83b0763afaa..f36bcc5b24f 100644 --- a/src/rules/noUnreachableRule.ts +++ b/src/rules/noUnreachableRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noUnusedExpressionRule.ts b/src/rules/noUnusedExpressionRule.ts index 8c61c904c7b..287398c81e1 100644 --- a/src/rules/noUnusedExpressionRule.ts +++ b/src/rules/noUnusedExpressionRule.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import * as Lint from "../lint"; 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 = { diff --git a/src/rules/noUnusedNewRule.ts b/src/rules/noUnusedNewRule.ts index bed03b04f66..87dc6a7daa6 100644 --- a/src/rules/noUnusedNewRule.ts +++ b/src/rules/noUnusedNewRule.ts @@ -15,8 +15,9 @@ * limitations under the License. */ -import * as Lint from "../lint"; import * as ts from "typescript"; + +import * as Lint from "../lint"; import { NoUnusedExpressionWalker } from "./noUnusedExpressionRule"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index 8dc6446bb0d..cf536202d72 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_REACT = "react"; @@ -81,7 +82,6 @@ export class Rule extends Lint.Rules.AbstractRule { } class NoUnusedVariablesWalker extends Lint.RuleWalker { - private languageService: ts.LanguageService; private skipBindingElement: boolean; private skipParameterDeclaration: boolean; private skipVariableDeclaration: boolean; @@ -91,9 +91,8 @@ class NoUnusedVariablesWalker extends Lint.RuleWalker { private isReactUsed: boolean; private reactImport: ts.NamespaceImport; - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, languageService: ts.LanguageService) { + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { super(sourceFile, options); - this.languageService = languageService; this.skipVariableDeclaration = false; this.skipParameterDeclaration = false; this.hasSeenJsxElement = false; diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index af701ac1458..1f126607356 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { @@ -45,11 +46,8 @@ export class Rule extends Lint.Rules.AbstractRule { type VisitedVariables = {[varName: string]: boolean}; class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { - private languageService: ts.LanguageService; - - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, languageService: ts.LanguageService) { + constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) { super(sourceFile, options); - this.languageService = languageService; } public createScope(): VisitedVariables { diff --git a/src/rules/noVarKeywordRule.ts b/src/rules/noVarKeywordRule.ts index 4ff788faf9e..79b9aa1ba0b 100644 --- a/src/rules/noVarKeywordRule.ts +++ b/src/rules/noVarKeywordRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/noVarRequiresRule.ts b/src/rules/noVarRequiresRule.ts index ad5e0f977f6..e6498d03773 100644 --- a/src/rules/noVarRequiresRule.ts +++ b/src/rules/noVarRequiresRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { @@ -42,10 +43,6 @@ export class Rule extends Lint.Rules.AbstractRule { } class NoVarRequiresWalker extends Lint.ScopeAwareRuleWalker<{}> { - constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { - super(sourceFile, options); - } - public createScope(): {} { return {}; } diff --git a/src/rules/objectLiteralSortKeysRule.ts b/src/rules/objectLiteralSortKeysRule.ts index 85eeda20931..01406838716 100644 --- a/src/rules/objectLiteralSortKeysRule.ts +++ b/src/rules/objectLiteralSortKeysRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/oneLineRule.ts b/src/rules/oneLineRule.ts index d077be8612e..e7bcfc0ef2e 100644 --- a/src/rules/oneLineRule.ts +++ b/src/rules/oneLineRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_BRACE = "check-open-brace"; diff --git a/src/rules/oneVariablePerDeclarationRule.ts b/src/rules/oneVariablePerDeclarationRule.ts index 84358facb21..3a79c213f24 100644 --- a/src/rules/oneVariablePerDeclarationRule.ts +++ b/src/rules/oneVariablePerDeclarationRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_IGNORE_FOR_LOOP = "ignore-for-loop"; diff --git a/src/rules/onlyArrowFunctionsRule.ts b/src/rules/onlyArrowFunctionsRule.ts index 8d09fd7d691..9abe86eeeae 100644 --- a/src/rules/onlyArrowFunctionsRule.ts +++ b/src/rules/onlyArrowFunctionsRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/orderedImportsRule.ts b/src/rules/orderedImportsRule.ts index 944a4238a72..547e33e5057 100644 --- a/src/rules/orderedImportsRule.ts +++ b/src/rules/orderedImportsRule.ts @@ -1,6 +1,24 @@ -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 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 = { diff --git a/src/rules/quotemarkRule.ts b/src/rules/quotemarkRule.ts index feebaa6c88b..aabfd9eaf32 100644 --- a/src/rules/quotemarkRule.ts +++ b/src/rules/quotemarkRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; enum QuoteMark { diff --git a/src/rules/radixRule.ts b/src/rules/radixRule.ts index e4b54269de0..5bfb33b9c49 100644 --- a/src/rules/radixRule.ts +++ b/src/rules/radixRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/restrictPlusOperandsRule.ts b/src/rules/restrictPlusOperandsRule.ts new file mode 100644 index 00000000000..50cea6b81eb --- /dev/null +++ b/src/rules/restrictPlusOperandsRule.ts @@ -0,0 +1,65 @@ +/** + * @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 ts from "typescript"; + +import * as Lint from "../lint"; + +export class Rule extends Lint.Rules.TypedRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "restrict-plus-operands", + description: "When adding two variables, operands must both be of type number or of type string.", + optionsDescription: "Not configurable.", + options: null, + optionExamples: ["true"], + type: "functionality", + requiresTypeInfo: true, + }; + /* 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 applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + return this.applyWithWalker(new RestrictPlusOperandsWalker(sourceFile, this.getOptions(), program)); + } +} + +class RestrictPlusOperandsWalker extends Lint.ProgramAwareRuleWalker { + public visitBinaryExpression(node: ts.BinaryExpression) { + if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) { + const tc = this.getTypeChecker(); + const leftType = tc.typeToString(tc.getTypeAtLocation(node.left)); + const rightType = tc.typeToString(tc.getTypeAtLocation(node.right)); + + const width = node.getWidth(); + const position = node.getStart(); + + if (leftType !== rightType) { + // mismatched types + this.addFailure(this.createFailure(position, width, Rule.MISMATCHED_TYPES_FAILURE)); + } else if (leftType !== "number" && leftType !== "string") { + // adding unsupported types + const failureString = Rule.UNSUPPORTED_TYPE_FAILURE_FACTORY(leftType); + this.addFailure(this.createFailure(position, width, failureString)); + } + } + + super.visitBinaryExpression(node); + } +} diff --git a/src/rules/semicolonRule.ts b/src/rules/semicolonRule.ts index 5f2c576cbc8..2ae5abbf10f 100644 --- a/src/rules/semicolonRule.ts +++ b/src/rules/semicolonRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_ALWAYS = "always"; diff --git a/src/rules/switchDefaultRule.ts b/src/rules/switchDefaultRule.ts index e597f4e6060..a9c3e6b4279 100644 --- a/src/rules/switchDefaultRule.ts +++ b/src/rules/switchDefaultRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 682c6c3d6e5..083922c592d 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/tripleEqualsRule.ts b/src/rules/tripleEqualsRule.ts index daea55fbb0a..87c4fbdfc00 100644 --- a/src/rules/tripleEqualsRule.ts +++ b/src/rules/tripleEqualsRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_ALLOW_NULL_CHECK = "allow-null-check"; diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index 5a5a6e9b2c2..0b5241cd39a 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/typedefWhitespaceRule.ts b/src/rules/typedefWhitespaceRule.ts index c9eec8ec4c7..182388fc6c1 100644 --- a/src/rules/typedefWhitespaceRule.ts +++ b/src/rules/typedefWhitespaceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; /* tslint:disable:object-literal-sort-keys */ diff --git a/src/rules/useIsnanRule.ts b/src/rules/useIsnanRule.ts index 1478bb23e21..5e37d0daf62 100644 --- a/src/rules/useIsnanRule.ts +++ b/src/rules/useIsnanRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/useStrictRule.ts b/src/rules/useStrictRule.ts index e4d800cbb30..d7aa8335c74 100644 --- a/src/rules/useStrictRule.ts +++ b/src/rules/useStrictRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; export class Rule extends Lint.Rules.AbstractRule { diff --git a/src/rules/variableNameRule.ts b/src/rules/variableNameRule.ts index d72f9597274..bd6bf1e9d3d 100644 --- a/src/rules/variableNameRule.ts +++ b/src/rules/variableNameRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; 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 1b294c54c58..88e59d03da8 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -16,6 +16,7 @@ */ import * as ts from "typescript"; + import * as Lint from "../lint"; const OPTION_BRANCH = "check-branch"; diff --git a/src/test.ts b/src/test.ts index ad138941e7b..7c36c643b13 100644 --- a/src/test.ts +++ b/src/test.ts @@ -20,10 +20,12 @@ import * as diff from "diff"; import * as fs from "fs"; import * as glob from "glob"; import * as path from "path"; +import * as ts from "typescript"; -import * as Linter from "./tslint"; -import * as parse from "./test/parse"; +import {createCompilerOptions} from "./language/utils"; import {LintError} from "./test/lintError"; +import * as parse from "./test/parse"; +import * as Linter from "./tslint"; const FILE_EXTENSION = ".lint"; @@ -46,17 +48,43 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ for (const fileToLint of filesToLint) { const fileBasename = path.basename(fileToLint, FILE_EXTENSION); + const fileCompileName = fileBasename.replace(/\.lint$/, ""); const fileText = fs.readFileSync(fileToLint, "utf8"); const fileTextWithoutMarkup = parse.removeErrorMarkup(fileText); const errorsFromMarkup = parse.parseErrorsFromMarkup(fileText); + const compilerOptions = createCompilerOptions(); + const compilerHost: ts.CompilerHost = { + fileExists: () => true, + getCanonicalFileName: (filename: string) => filename, + getCurrentDirectory: () => "", + getDefaultLibFileName: () => ts.getDefaultLibFileName(compilerOptions), + getDirectories: () => [], + getNewLine: () => "\n", + getSourceFile: function (filenameToGet: string) { + if (filenameToGet === this.getDefaultLibFileName()) { + const fileText = fs.readFileSync(ts.getDefaultLibFilePath(compilerOptions)).toString(); + return ts.createSourceFile(filenameToGet, fileText, compilerOptions.target); + } else if (filenameToGet === fileCompileName) { + return ts.createSourceFile(fileBasename, fileTextWithoutMarkup, compilerOptions.target, true); + } + }, + readFile: () => null, + useCaseSensitiveFileNames: () => true, + writeFile: () => null, + }; + + const program = ts.createProgram([fileCompileName], compilerOptions, compilerHost); + // perform type checking on the program, updating nodes with symbol table references + ts.getPreEmitDiagnostics(program); + const lintOptions = { configuration: tslintConfig, formatter: "prose", formattersDirectory: "", rulesDirectory, }; - const linter = new Linter(fileBasename, fileTextWithoutMarkup, lintOptions); + const linter = new Linter(fileBasename, fileTextWithoutMarkup, lintOptions, program); const errorsFromLinter: LintError[] = linter.lint().failures.map((failure) => { const startLineAndCharacter = failure.getStartPosition().getLineAndCharacter(); const endLineAndCharacter = failure.getEndPosition().getLineAndCharacter(); diff --git a/src/test/parse.ts b/src/test/parse.ts index 90d0fb185f3..24366ecbc58 100644 --- a/src/test/parse.ts +++ b/src/test/parse.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import {LintError, errorComparator, lintSyntaxError} from "./lintError"; import { - Line, - ErrorLine, CodeLine, - MultilineErrorLine, EndErrorLine, + ErrorLine, + Line, MessageSubstitutionLine, + MultilineErrorLine, parseLine, printLine, } from "./lines"; +import {LintError, errorComparator, lintSyntaxError} from "./lintError"; /** * Takes the full text of a .lint file and returns the contents of the file diff --git a/src/tsconfig.json b/src/tsconfig.json index f51978a973e..9d1a4a5a91b 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -59,9 +59,11 @@ "language/languageServiceHost.ts", "language/rule/abstractRule.ts", "language/rule/rule.ts", + "language/rule/typedRule.ts", "language/utils.ts", "language/walker/blockScopeAwareRuleWalker.ts", "language/walker/index.ts", + "language/walker/programAwareRuleWalker.ts", "language/walker/ruleWalker.ts", "language/walker/scopeAwareRuleWalker.ts", "language/walker/skippableTokenAwareRuleWalker.ts", @@ -124,6 +126,7 @@ "rules/orderedImportsRule.ts", "rules/quotemarkRule.ts", "rules/radixRule.ts", + "rules/restrictPlusOperandsRule.ts", "rules/semicolonRule.ts", "rules/switchDefaultRule.ts", "rules/trailingCommaRule.ts", diff --git a/src/tslint-cli.ts b/src/tslint-cli.ts index 07956698acd..36f0a869fcd 100644 --- a/src/tslint-cli.ts +++ b/src/tslint-cli.ts @@ -19,19 +19,21 @@ import * as fs from "fs"; import * as glob from "glob"; import * as optimist from "optimist"; import * as path from "path"; -import * as Linter from "./tslint"; +import * as ts from "typescript"; + import { CONFIG_FILENAME, DEFAULT_CONFIG, findConfiguration, } from "./configuration"; import {consoleTestResultHandler, runTest} from "./test"; +import * as Linter from "./tslint"; let processed = optimist .usage("Usage: $0 [options] file ...") .check((argv: any) => { - // at least one of file, help, version or unqualified argument must be present - if (!(argv.h || argv.i || argv.test || argv.v || argv._.length > 0)) { + // at least one of file, help, version, project or unqualified argument must be present + if (!(argv.h || argv.i || argv.test || argv.v || argv.project || argv._.length > 0)) { throw "Missing files"; } @@ -80,6 +82,12 @@ let processed = optimist "test": { describe: "test that tslint produces the correct output for the specified directory", }, + "project": { + describe: "tsconfig.json file", + }, + "type-check": { + describe: "enable type checking when linting a project", + }, "v": { alias: "version", describe: "current version", @@ -185,6 +193,14 @@ tslint accepts the following commandline 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. @@ -201,7 +217,7 @@ if (argv.c && !fs.existsSync(argv.c)) { } const possibleConfigAbsolutePath = argv.c != null ? path.resolve(argv.c) : null; -const processFile = (file: string) => { +const processFile = (file: string, program?: ts.Program) => { if (!fs.existsSync(file)) { console.error(`Unable to open file: ${file}`); process.exit(1); @@ -231,7 +247,7 @@ const processFile = (file: string) => { formatter: argv.t, formattersDirectory: argv.s, rulesDirectory: argv.r, - }); + }, program); const lintResult = linter.lint(); @@ -242,8 +258,41 @@ const processFile = (file: string) => { } }; -const files = argv._; +// if both files and tsconfig are present, use files +let files = argv._; +let program: ts.Program; + +if (argv.project != null) { + if (!fs.existsSync(argv.project)) { + console.error("Invalid option for project: " + argv.project); + process.exit(1); + } + program = Linter.createProgram(argv.project, path.dirname(argv.project)); + if (files.length === 0) { + files = Linter.getFileNames(program); + } + if (argv["type-check"]) { + // if type checking, run the type checker + const diagnostics = ts.getPreEmitDiagnostics(program); + if (diagnostics.length > 0) { + const messages = diagnostics.map((diag) => { + // emit any error messages + let message = ts.DiagnosticCategory[diag.category]; + if (diag.file) { + const {line, character} = diag.file.getLineAndCharacterOfPosition(diag.start); + message += ` at ${diag.file.fileName}:${line + 1}:${character + 1}:`; + } + message += " " + ts.flattenDiagnosticMessageText(diag.messageText, "\n"); + return message; + }); + throw new Error(messages.join("\n")); + } + } else { + // if not type checking, we don't need to pass in a program object + program = undefined; + } +} for (const file of files) { - glob.sync(file, { ignore: argv.e }).forEach(processFile); + glob.sync(file, { ignore: argv.e }).forEach((file) => processFile(file, program)); } diff --git a/src/tslint.ts b/src/tslint.ts index baab7ee144b..a7438160e3f 100644 --- a/src/tslint.ts +++ b/src/tslint.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { IFormatter } from "./language/formatter/formatter"; -import { RuleFailure } from "./language/rule/rule"; -import { getSourceFile } from "./language/utils"; +import * as ts from "typescript"; + import { DEFAULT_CONFIG, findConfiguration, @@ -28,31 +27,76 @@ import { } from "./configuration"; import { EnableDisableRulesWalker } from "./enableDisableRules"; import { findFormatter } from "./formatterLoader"; -import { ILinterOptionsRaw, ILinterOptions, LintResult } from "./lint"; +import { IFormatter } from "./language/formatter/formatter"; +import { RuleFailure } from "./language/rule/rule"; +import { TypedRule } from "./language/rule/typedRule"; +import { getSourceFile } from "./language/utils"; +import { ILinterOptions, ILinterOptionsRaw, LintResult } from "./lint"; import { loadRules } from "./ruleLoader"; import { arrayify } from "./utils"; class Linter { - public static VERSION = "3.13.0-dev.0"; + public static VERSION = "3.14.0-dev.0"; public static findConfiguration = findConfiguration; public static findConfigurationPath = findConfigurationPath; public static getRulesDirectories = getRulesDirectories; public static loadConfigurationFromPath = loadConfigurationFromPath; - private fileName: string; - private source: string; private options: ILinterOptions; - constructor(fileName: string, source: string, options: ILinterOptionsRaw) { - this.fileName = fileName; - this.source = source; + /** + * Creates a TypeScript program object from a tsconfig.json file path and optional project directory. + */ + public static createProgram(configFile: string, projectDirectory?: string): ts.Program { + if (projectDirectory === undefined) { + const lastSeparator = configFile.lastIndexOf("/"); + if (lastSeparator < 0) { + projectDirectory = "."; + } else { + projectDirectory = configFile.substring(0, lastSeparator + 1); + } + } + + const {config} = ts.readConfigFile(configFile, ts.sys.readFile); + const parsed = ts.parseJsonConfigFileContent(config, { + fileExists: (path: string) => true, + readDirectory: ts.sys.readDirectory, + useCaseSensitiveFileNames: false, + }, projectDirectory); + const host = ts.createCompilerHost(parsed.options, true); + const program = ts.createProgram(parsed.fileNames, parsed.options, host); + + return program; + } + + /** + * 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 program.getSourceFiles().map(s => s.fileName).filter(l => l.substr(-5) !== ".d.ts"); + } + + constructor(private fileName: string, + private source: string, + options: ILinterOptionsRaw, + private program?: ts.Program) { this.options = this.computeFullOptions(options); } public lint(): LintResult { const failures: RuleFailure[] = []; - const sourceFile = getSourceFile(this.fileName, this.source); + let sourceFile: ts.SourceFile; + if (this.program) { + sourceFile = this.program.getSourceFile(this.fileName); + // check if the program has been type checked + if (!("resolvedModules" in sourceFile)) { + throw new Error("Program must be type checked before linting"); + } + } else { + sourceFile = getSourceFile(this.fileName, this.source); + } // walk the code first to find all the intervals where rules are disabled const rulesWalker = new EnableDisableRulesWalker(sourceFile, { @@ -67,7 +111,12 @@ class Linter { const configuredRules = loadRules(configuration, enableDisableRuleMap, rulesDirectories); const enabledRules = configuredRules.filter((r) => r.isEnabled()); for (let rule of enabledRules) { - const ruleFailures = rule.apply(sourceFile); + 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(failures, ruleFailure)) { failures.push(ruleFailure); diff --git a/test/check-bin.sh b/test/check-bin.sh index 07c65f364ec..f696254ac3b 100755 --- a/test/check-bin.sh +++ b/test/check-bin.sh @@ -108,6 +108,14 @@ expectOut $? 0 "tslint --test did not exit correctly for a passing test with cus ./bin/tslint -r test/files/custom-rules-2 --test test/files/custom-rule-cli-rule-test expectOut $? 0 "tslint --test did not exit correctly for a passing test with custom rules from the CLI" +# make sure tslint exits correctly when tsconfig is specified but no files are given +./bin/tslint -c test/files/tsconfig-test/tslint.json --project test/files/tsconfig-test/tsconfig.json +expectOut $? 0 "tslint with tsconfig did not exit correctly" + +# make sure tslint only lints files given if tsconfig is also specified +./bin/tslint -c test/files/tsconfig-test/tslint.json --project test/files/tsconfig-test/tsconfig.json test/files/tsconfig-test/other.test.ts +expectOut $? 2 "tslint with tsconfig and files did not find lint failures from given files" + if [ $num_failures != 0 ]; then echo "Failed $num_failures tests" exit 1 diff --git a/test/configurationTests.ts b/test/configurationTests.ts index c2f018bae73..a6e25cc04f3 100644 --- a/test/configurationTests.ts +++ b/test/configurationTests.ts @@ -14,9 +14,10 @@ * limitations under the License. */ +import * as fs from "fs"; import * as os from "os"; import * as path from "path"; -import * as fs from "fs"; + import {IConfigurationFile, extendConfigurationFile, loadConfigurationFromPath} from "../src/configuration"; describe("Configuration", () => { diff --git a/test/files/tsconfig-test/good.test.ts b/test/files/tsconfig-test/good.test.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/files/tsconfig-test/other.test.ts b/test/files/tsconfig-test/other.test.ts new file mode 100644 index 00000000000..296d5492b00 --- /dev/null +++ b/test/files/tsconfig-test/other.test.ts @@ -0,0 +1 @@ +console.log(1); diff --git a/test/files/tsconfig-test/tsconfig.json b/test/files/tsconfig-test/tsconfig.json new file mode 100644 index 00000000000..fa3a484bd90 --- /dev/null +++ b/test/files/tsconfig-test/tsconfig.json @@ -0,0 +1,5 @@ +{ + "files": [ + "good.test.ts" + ] +} diff --git a/test/files/tsconfig-test/tslint.json b/test/files/tsconfig-test/tslint.json new file mode 100644 index 00000000000..7be5efded55 --- /dev/null +++ b/test/files/tsconfig-test/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": [true, "log"] + } +} diff --git a/test/formatters/checkstyleFormatterTests.ts b/test/formatters/checkstyleFormatterTests.ts index 72aa0ef8a05..c9428068ce7 100644 --- a/test/formatters/checkstyleFormatterTests.ts +++ b/test/formatters/checkstyleFormatterTests.ts @@ -1,4 +1,5 @@ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("Checkstyle Formatter", () => { diff --git a/test/formatters/externalFormatterTest.ts b/test/formatters/externalFormatterTest.ts index 69b2cf23507..12733cf73e5 100644 --- a/test/formatters/externalFormatterTest.ts +++ b/test/formatters/externalFormatterTest.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("External Formatter", () => { diff --git a/test/formatters/jsonFormatterTests.ts b/test/formatters/jsonFormatterTests.ts index 60e82b58c2c..eccdc6496f6 100644 --- a/test/formatters/jsonFormatterTests.ts +++ b/test/formatters/jsonFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("JSON Formatter", () => { diff --git a/test/formatters/msbuildFormatterTests.ts b/test/formatters/msbuildFormatterTests.ts index 937d97e527b..2560f8fb473 100644 --- a/test/formatters/msbuildFormatterTests.ts +++ b/test/formatters/msbuildFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("MSBuild Formatter", () => { diff --git a/test/formatters/pmdFormatterTests.ts b/test/formatters/pmdFormatterTests.ts index 779eeb1826b..2e95f243b39 100644 --- a/test/formatters/pmdFormatterTests.ts +++ b/test/formatters/pmdFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("PMD Formatter", () => { diff --git a/test/formatters/proseFormatterTests.ts b/test/formatters/proseFormatterTests.ts index b61d1b2ea94..ecde9fc00d2 100644 --- a/test/formatters/proseFormatterTests.ts +++ b/test/formatters/proseFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("Prose Formatter", () => { diff --git a/test/formatters/verboseFormatterTests.ts b/test/formatters/verboseFormatterTests.ts index 223ba7c2960..2cb494fa845 100644 --- a/test/formatters/verboseFormatterTests.ts +++ b/test/formatters/verboseFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("Verbose Formatter", () => { diff --git a/test/formatters/vsoFormatterTests.ts b/test/formatters/vsoFormatterTests.ts index ba54c6ca054..77d01d26371 100644 --- a/test/formatters/vsoFormatterTests.ts +++ b/test/formatters/vsoFormatterTests.ts @@ -15,6 +15,7 @@ */ import * as ts from "typescript"; + import {IFormatter, RuleFailure, TestUtils} from "../lint"; describe("VSO Formatter", () => { diff --git a/test/rule-tester/parseTests.ts b/test/rule-tester/parseTests.ts index 471735b32f4..40bc3722206 100644 --- a/test/rule-tester/parseTests.ts +++ b/test/rule-tester/parseTests.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import * as testData from "./testData"; import * as parse from "../../src/test/parse"; +import * as testData from "./testData"; describe("Rule Test Parse", () => { describe("removeErrorMarkup", () => { diff --git a/test/ruleTestRunner.ts b/test/ruleTestRunner.ts index a8406e5eee5..250cfe30d1a 100644 --- a/test/ruleTestRunner.ts +++ b/test/ruleTestRunner.ts @@ -18,7 +18,7 @@ import * as colors from "colors"; import * as glob from "glob"; import * as path from "path"; -import {runTest, consoleTestResultHandler} from "../src/test"; +import {consoleTestResultHandler, runTest} from "../src/test"; // needed to get colors to show up when passing through Grunt (colors as any).enabled = true; diff --git a/test/rules/_integration/enable-disable/test.ts.lint b/test/rules/_integration/enable-disable/test.ts.lint index ba78e3b5740..a628d1bc4d8 100644 --- a/test/rules/_integration/enable-disable/test.ts.lint +++ b/test/rules/_integration/enable-disable/test.ts.lint @@ -25,6 +25,19 @@ var AAAaA = 'test' /* tslint:enable:zasdadsa */ var AAAaA = 'test' +/* tslint:enable quotemark */ +var AAAaA = 'test' + ~~~~~~ [' should be "] +/* tslint:disable */ +var AAAaA = 'test' +/* tslint:enable quotemark variable-name */ +var AAAaA = 'test' + ~~~~~ [variable name must be in camelcase or uppercase] + ~~~~~~ [' should be "] +/* tslint:disable quotemark */ +var AAAaA = 'test' + ~~~~~ [variable name must be in camelcase or uppercase] + /* tslint:enable */ var re; re = /`/; diff --git a/test/rules/restrict-plus-operands/test.ts.lint b/test/rules/restrict-plus-operands/test.ts.lint new file mode 100644 index 00000000000..b289818c685 --- /dev/null +++ b/test/rules/restrict-plus-operands/test.ts.lint @@ -0,0 +1,60 @@ +// aliases for number +type MyNumber = number; +type MyString = string; +interface NumberStringPair { + first: MyNumber, + second: MyString +} + +var x = 5; +var y = "10"; +var z = 8.2; +var w = "6.5"; +var pair: NumberStringPair = { + first: 5, + second: "10" +}; + +// bad +var bad1 = 5 + "10"; + ~~~~~~~~ [Types of values used in '+' operation must match] +var bad2 = [] + 5; + ~~~~~~ [Types of values used in '+' operation must match] +var bad3 = [] + {}; + ~~~~~~~ [Types of values used in '+' operation must match] +var bad4 = [] + []; + ~~~~~~~ [cannot add type undefined[]] +var bad4 = 5 + []; + ~~~~~~ [Types of values used in '+' operation must match] +var bad5 = "5" + {}; + ~~~~~~~~ [Types of values used in '+' operation must match] +var bad6 = 5.5 + "5"; + ~~~~~~~~~ [Types of values used in '+' operation must match] +var bad7 = "5.5" + 5; + ~~~~~~~~~ [Types of values used in '+' operation must match] +var bad8 = x + y; + ~~~~~ [Types of values used in '+' operation must match] +var bad9 = y + x; + ~~~~~ [Types of values used in '+' operation must match] +var bad10 = x + {}; + ~~~~~~ [Types of values used in '+' operation must match] +var bad11 = [] + y; + ~~~~~~ [Types of values used in '+' operation must match] +var bad12 = pair.first + "10"; + ~~~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] +var bad13 = 5 + pair.second; + ~~~~~~~~~~~~~~~ [Types of values used in '+' operation must match] +var bad14 = pair + pair; + ~~~~~~~~~~~ [cannot add type NumberStringPair] + +// good +var good1 = 5 + 10; +var good2 = "5.5" + "10"; +var good3 = parseFloat("5.5", 10), + 10; +var good4 = x + z; +var good5 = y + w; + +var good6 = pair.first + 10; +var good7 = pair.first + (10 as MyNumber); +var good8 = "5.5" + pair.second; +var good9 = ("5.5" as MyString) + pair.second; diff --git a/test/rules/restrict-plus-operands/tslint.json b/test/rules/restrict-plus-operands/tslint.json new file mode 100644 index 00000000000..8389b1de47a --- /dev/null +++ b/test/rules/restrict-plus-operands/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "restrict-plus-operands": true + } +} diff --git a/test/tsconfig.json b/test/tsconfig.json index d2d960823da..54784774e94 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -52,9 +52,11 @@ "../src/language/languageServiceHost.ts", "../src/language/rule/abstractRule.ts", "../src/language/rule/rule.ts", + "../src/language/rule/typedRule.ts", "../src/language/utils.ts", "../src/language/walker/blockScopeAwareRuleWalker.ts", "../src/language/walker/index.ts", + "../src/language/walker/programAwareRuleWalker.ts", "../src/language/walker/ruleWalker.ts", "../src/language/walker/scopeAwareRuleWalker.ts", "../src/language/walker/skippableTokenAwareRuleWalker.ts", @@ -99,7 +101,6 @@ "../src/rules/noInvalidThisRule.ts", "../src/rules/noMergeableNamespaceRule.ts", "../src/rules/noNamespaceRule.ts", - "../src/rules/noUnusedNewRule.ts", "../src/rules/noNullKeywordRule.ts", "../src/rules/noReferenceRule.ts", "../src/rules/noRequireImportsRule.ts", @@ -109,6 +110,7 @@ "../src/rules/noTrailingWhitespaceRule.ts", "../src/rules/noUnreachableRule.ts", "../src/rules/noUnusedExpressionRule.ts", + "../src/rules/noUnusedNewRule.ts", "../src/rules/noUnusedVariableRule.ts", "../src/rules/noUseBeforeDeclareRule.ts", "../src/rules/noVarKeywordRule.ts", @@ -120,6 +122,7 @@ "../src/rules/orderedImportsRule.ts", "../src/rules/quotemarkRule.ts", "../src/rules/radixRule.ts", + "../src/rules/restrictPlusOperandsRule.ts", "../src/rules/semicolonRule.ts", "../src/rules/switchDefaultRule.ts", "../src/rules/trailingCommaRule.ts", diff --git a/test/utils.ts b/test/utils.ts index e436ca725ed..d3f3c580105 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -17,6 +17,7 @@ import * as fs from "fs"; import * as path from "path"; import * as ts from "typescript"; + import * as Lint from "./lint"; export function getSourceFile(fileName: string): ts.SourceFile { diff --git a/typings/findup-sync/findup-sync.d.ts b/typings/findup-sync/findup-sync.d.ts index f42ca325613..b7bf674e231 100644 --- a/typings/findup-sync/findup-sync.d.ts +++ b/typings/findup-sync/findup-sync.d.ts @@ -13,7 +13,6 @@ declare module 'findup-sync' { } function mod(pattern: string[] | string, opts?: IOptions): string; - namespace mod {} // Literally works around a bug in TS export = mod; }