diff --git a/package.json b/package.json index 2a325587be6..be75b61b3e6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "glob": "^7.1.1", "optimist": "~0.6.0", "resolve": "^1.1.7", + "tsutils": "^1.0.0", "update-notifier": "^2.0.0" }, "peerDependencies": { @@ -68,7 +69,7 @@ "rimraf": "^2.5.4", "tslint": "latest", "tslint-test-config-non-relative": "file:test/external/tslint-test-config-non-relative", - "typescript": "2.1.4" + "typescript": "^2.1.6" }, "license": "Apache-2.0", "engines": { diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index 031ad1af737..7f6cb0279e9 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -15,10 +15,10 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import {AbstractRule} from "./language/rule/abstractRule"; -import {forEachComment, TokenPosition} from "./language/utils"; import {IEnableDisablePosition} from "./ruleLoader"; export class EnableDisableRulesWalker { @@ -42,11 +42,11 @@ export class EnableDisableRulesWalker { } public getEnableDisableRuleMap() { - forEachComment(this.sourceFile, (fullText, kind, pos) => { - const commentText = kind === ts.SyntaxKind.SingleLineCommentTrivia - ? fullText.substring(pos.tokenStart + 2, pos.end) - : fullText.substring(pos.tokenStart + 2, pos.end - 2); - return this.handleComment(commentText, pos); + utils.forEachComment(this.sourceFile, (fullText, comment) => { + const commentText = comment.kind === ts.SyntaxKind.SingleLineCommentTrivia + ? fullText.substring(comment.pos + 2, comment.end) + : fullText.substring(comment.pos + 2, comment.end - 2); + return this.handleComment(commentText, comment); }); return this.enableDisableRuleMap; @@ -85,7 +85,7 @@ export class EnableDisableRulesWalker { } } - private handleComment(commentText: string, pos: TokenPosition) { + private handleComment(commentText: string, range: ts.TextRange) { // regex is: start of string followed by any amount of whitespace // followed by tslint and colon // followed by either "enable" or "disable" @@ -110,32 +110,32 @@ export class EnableDisableRulesWalker { rulesList = this.enabledRules; } - this.handleTslintLineSwitch(rulesList, match[1] === "enable", match[2], pos); + this.handleTslintLineSwitch(rulesList, match[1] === "enable", match[2], range); } } - private handleTslintLineSwitch(rules: string[], isEnabled: boolean, modifier: string, pos: TokenPosition) { + private handleTslintLineSwitch(rules: string[], isEnabled: boolean, modifier: string, range: ts.TextRange) { let start: number | undefined; let end: number | undefined; if (modifier === "line") { // start at the beginning of the line where comment starts - start = this.getStartOfLinePosition(pos.tokenStart)!; + start = this.getStartOfLinePosition(range.pos)!; // end at the beginning of the line following the comment - end = this.getStartOfLinePosition(pos.end, 1); + end = this.getStartOfLinePosition(range.end, 1); } else if (modifier === "next-line") { // start at the beginning of the line following the comment - start = this.getStartOfLinePosition(pos.end, 1); + start = this.getStartOfLinePosition(range.end, 1); if (start === undefined) { // no need to switch anything, there is no next line return; } // end at the beginning of the line following the next line - end = this.getStartOfLinePosition(pos.end, 2); + end = this.getStartOfLinePosition(range.end, 2); } else { // switch rule for the rest of the file // start at the current position, but skip end position - start = pos.tokenStart; + start = range.pos; end = undefined; } diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index c44db5ee8ba..34036d8464f 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -114,32 +115,13 @@ interface Overload { name: string; } -function isLiteralExpression(node: ts.Node): node is ts.LiteralExpression { - return node.kind === ts.SyntaxKind.StringLiteral || node.kind === ts.SyntaxKind.NumericLiteral; -} - export function getOverloadKey(node: ts.SignatureDeclaration): string | undefined { const o = getOverload(node); return o && o.key; } function getOverloadIfSignature(node: ts.TypeElement | ts.ClassElement): Overload | undefined { - return isSignatureDeclaration(node) ? getOverload(node) : undefined; -} - -export function isSignatureDeclaration(node: ts.Node): node is ts.SignatureDeclaration { - switch (node.kind) { - case ts.SyntaxKind.ConstructSignature: - case ts.SyntaxKind.Constructor: - case ts.SyntaxKind.CallSignature: - case ts.SyntaxKind.CallSignature: - case ts.SyntaxKind.MethodSignature: - case ts.SyntaxKind.MethodDeclaration: - case ts.SyntaxKind.FunctionDeclaration: - return true; - default: - return false; - } + return utils.isSignatureDeclaration(node) ? getOverload(node) : undefined; } function getOverload(node: ts.SignatureDeclaration): Overload | undefined { @@ -173,8 +155,8 @@ function getPropertyInfo(name: ts.PropertyName): { name: string, computed?: bool return { name: (name as ts.Identifier).text }; case ts.SyntaxKind.ComputedPropertyName: const { expression } = (name as ts.ComputedPropertyName); - return isLiteralExpression(expression) ? { name: expression.text } : { name: expression.getText(), computed: true }; + return utils.isLiteralExpression(expression) ? { name: expression.text } : { name: expression.getText(), computed: true }; default: - return isLiteralExpression(name) ? { name: (name as ts.StringLiteral).text } : undefined; + return utils.isLiteralExpression(name) ? { name: (name as ts.StringLiteral).text } : undefined; } } diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index 995ec565203..12604bd1d3d 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -117,10 +118,10 @@ class CommentWalker extends Lint.RuleWalker { } public visitSourceFile(node: ts.SourceFile) { - Lint.forEachComment(node, (fullText, kind, pos) => { - if (kind === ts.SyntaxKind.SingleLineCommentTrivia) { - const commentText = fullText.substring(pos.tokenStart, pos.end); - const startPosition = pos.tokenStart + 2; + utils.forEachComment(node, (fullText, comment) => { + if (comment.kind === ts.SyntaxKind.SingleLineCommentTrivia) { + const commentText = fullText.substring(comment.pos, comment.end); + const startPosition = comment.pos + 2; const width = commentText.length - 2; if (this.hasOption(OPTION_SPACE)) { if (!startsWithSpace(commentText)) { diff --git a/src/rules/importBlacklistRule.ts b/src/rules/importBlacklistRule.ts index 73350c779cf..fd8d97c17e6 100644 --- a/src/rules/importBlacklistRule.ts +++ b/src/rules/importBlacklistRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -66,7 +67,7 @@ class ImportBlacklistWalker extends Lint.RuleWalker { } public visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration) { - if (isExternalModuleReference(node.moduleReference) && + if (utils.isExternalModuleReference(node.moduleReference) && node.moduleReference.expression !== undefined) { // If it's an import require and not an import alias this.checkForBannedImport(node.moduleReference.expression); @@ -80,7 +81,7 @@ class ImportBlacklistWalker extends Lint.RuleWalker { } private checkForBannedImport(expression: ts.Expression) { - if (isStringLiteral(expression) && this.hasOption(expression.text)) { + if (utils.isTextualLiteral(expression) && this.hasOption(expression.text)) { this.addFailureFromStartToEnd( expression.getStart(this.getSourceFile()) + 1, expression.getEnd() - 1, @@ -89,12 +90,3 @@ class ImportBlacklistWalker extends Lint.RuleWalker { } } } - -function isStringLiteral(node: ts.Node): node is ts.LiteralExpression { - return node.kind === ts.SyntaxKind.StringLiteral || - node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral; -} - -function isExternalModuleReference(node: ts.ModuleReference): node is ts.ExternalModuleReference { - return node.kind === ts.SyntaxKind.ExternalModuleReference; -} diff --git a/src/rules/jsdocFormatRule.ts b/src/rules/jsdocFormatRule.ts index f6c9b0ebe24..9645c1db607 100644 --- a/src/rules/jsdocFormatRule.ts +++ b/src/rules/jsdocFormatRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -50,9 +51,9 @@ export class Rule extends Lint.Rules.AbstractRule { class JsdocWalker extends Lint.RuleWalker { public visitSourceFile(node: ts.SourceFile) { - Lint.forEachComment(node, (fullText, kind, pos) => { - if (kind === ts.SyntaxKind.MultiLineCommentTrivia) { - this.findFailuresForJsdocComment(fullText.substring(pos.tokenStart, pos.end), pos.tokenStart); + utils.forEachComment(node, (fullText, comment) => { + if (comment.kind === ts.SyntaxKind.MultiLineCommentTrivia) { + this.findFailuresForJsdocComment(fullText.substring(comment.pos, comment.end), comment.pos); } }); } diff --git a/src/rules/newlineBeforeReturnRule.ts b/src/rules/newlineBeforeReturnRule.ts index 3465652c6bf..f94c99103be 100644 --- a/src/rules/newlineBeforeReturnRule.ts +++ b/src/rules/newlineBeforeReturnRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -47,7 +48,7 @@ class NewlineBeforeReturnWalker extends Lint.RuleWalker { super.visitReturnStatement(node); const parent = node.parent!; - if (!isBlockLike(parent)) { + if (!utils.isBlockLike(parent)) { // `node` is the only statement within this "block scope". No need to do any further validation. return; } @@ -75,7 +76,3 @@ class NewlineBeforeReturnWalker extends Lint.RuleWalker { } } } - -function isBlockLike(node: ts.Node): node is ts.BlockLike { - return "statements" in node; -} diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index a6b80204bcf..a1193ec9c5a 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -73,29 +74,29 @@ export class Rule extends Lint.Rules.AbstractRule { function walk(ctx: Lint.WalkContext) { let lastSeenWasWhitespace = false; let lastSeenWhitespacePosition = 0; - Lint.forEachToken(ctx.sourceFile, false, (fullText, kind, pos) => { + utils.forEachTokenWithTrivia(ctx.sourceFile, (fullText, kind, range) => { if (kind === ts.SyntaxKind.NewLineTrivia || kind === ts.SyntaxKind.EndOfFileToken) { if (lastSeenWasWhitespace) { - reportFailure(ctx, lastSeenWhitespacePosition, pos.tokenStart); + reportFailure(ctx, lastSeenWhitespacePosition, range.pos); } lastSeenWasWhitespace = false; } else if (kind === ts.SyntaxKind.WhitespaceTrivia) { lastSeenWasWhitespace = true; - lastSeenWhitespacePosition = pos.tokenStart; + lastSeenWhitespacePosition = range.pos; } else { if (ctx.options !== IgnoreOption.Comments) { if (kind === ts.SyntaxKind.SingleLineCommentTrivia) { - const commentText = fullText.substring(pos.tokenStart + 2, pos.end); + const commentText = fullText.substring(range.pos + 2, range.end); const match = /\s+$/.exec(commentText); if (match !== null) { - reportFailure(ctx, pos.end - match[0].length, pos.end); + reportFailure(ctx, range.end - match[0].length, range.end); } } else if (kind === ts.SyntaxKind.MultiLineCommentTrivia && (ctx.options !== IgnoreOption.JsDoc || - fullText[pos.tokenStart + 2] !== "*" || - fullText[pos.tokenStart + 3] === "*")) { - let startPos = pos.tokenStart + 2; - const commentText = fullText.substring(startPos, pos.end - 2); + fullText[range.pos + 2] !== "*" || + fullText[range.pos + 3] === "*")) { + let startPos = range.pos + 2; + const commentText = fullText.substring(startPos, range.end - 2); const lines = commentText.split("\n"); // we don't want to check the content of the last comment line, as it is always followed by */ const len = lines.length - 1; diff --git a/src/rules/noUnnecessaryInitializerRule.ts b/src/rules/noUnnecessaryInitializerRule.ts index f722b3f1ed3..c3fda70d632 100644 --- a/src/rules/noUnnecessaryInitializerRule.ts +++ b/src/rules/noUnnecessaryInitializerRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -45,7 +46,7 @@ export class Rule extends Lint.Rules.AbstractRule { class Walker extends Lint.RuleWalker { public visitVariableDeclaration(node: ts.VariableDeclaration) { - if (isBindingPattern(node.name)) { + if (utils.isBindingPattern(node.name)) { for (const elem of node.name.elements) { if (elem.kind === ts.SyntaxKind.BindingElement) { this.checkInitializer(elem); @@ -115,7 +116,3 @@ function isUndefined(node: ts.Node | undefined): boolean { node.kind === ts.SyntaxKind.Identifier && (node as ts.Identifier).originalKeywordKind === ts.SyntaxKind.UndefinedKeyword; } - -function isBindingPattern(node: ts.Node): node is ts.ArrayBindingPattern | ts.ObjectBindingPattern { - return node.kind === ts.SyntaxKind.ArrayBindingPattern || node.kind === ts.SyntaxKind.ObjectBindingPattern; -} diff --git a/src/rules/noUnnecessaryQualifierRule.ts b/src/rules/noUnnecessaryQualifierRule.ts index d2c1e5c7bcb..8d4e4232c7a 100644 --- a/src/rules/noUnnecessaryQualifierRule.ts +++ b/src/rules/noUnnecessaryQualifierRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -67,7 +68,7 @@ class Walker extends Lint.ProgramAwareRuleWalker { break; case ts.SyntaxKind.PropertyAccessExpression: const { expression, name } = node as ts.PropertyAccessExpression; - if (isEntityNameExpression(expression)) { + if (utils.isEntityNameExpression(expression)) { this.visitNamespaceAccess(node, expression, name); break; } @@ -126,11 +127,6 @@ class Walker extends Lint.ProgramAwareRuleWalker { } } -function isEntityNameExpression(expr: ts.Expression): expr is ts.EntityNameExpression { - return expr.kind === ts.SyntaxKind.Identifier || - expr.kind === ts.SyntaxKind.PropertyAccessExpression && isEntityNameExpression((expr as ts.PropertyAccessExpression).expression); -} - // TODO: Should just be `===`. See https://github.com/palantir/tslint/issues/1969 function nodesAreEqual(a: ts.Node, b: ts.Node) { return a.pos === b.pos; diff --git a/src/rules/noUnsafeAnyRule.ts b/src/rules/noUnsafeAnyRule.ts index 90e7fe16f87..14851adf82e 100644 --- a/src/rules/noUnsafeAnyRule.ts +++ b/src/rules/noUnsafeAnyRule.ts @@ -42,9 +42,12 @@ export class Rule extends Lint.Rules.TypedRule { } } +// This is marked @internal, but we need it! +const isExpression: (node: ts.Node) => node is ts.Expression = (ts as any).isExpression; + class Walker extends Lint.ProgramAwareRuleWalker { public visitNode(node: ts.Node) { - if (ts.isExpression(node) && isAny(this.getType(node)) && !this.isAllowedLocation(node)) { + if (isExpression(node) && isAny(this.getType(node)) && !this.isAllowedLocation(node)) { this.addFailureAtNode(node, Rule.FAILURE_STRING); } else { super.visitNode(node); @@ -111,8 +114,3 @@ class Walker extends Lint.ProgramAwareRuleWalker { function isAny(type: ts.Type | undefined): boolean { return type !== undefined && Lint.isTypeFlagSet(type, ts.TypeFlags.Any); } - -// This is marked @internal, but we need it! -declare module "typescript" { - export function isExpression(node: ts.Node): node is ts.Expression; -} diff --git a/src/rules/noUnsafeFinallyRule.ts b/src/rules/noUnsafeFinallyRule.ts index f6044afff0a..6c84729b15b 100644 --- a/src/rules/noUnsafeFinallyRule.ts +++ b/src/rules/noUnsafeFinallyRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -192,14 +193,10 @@ function isFinallyBlock(node: ts.Node): boolean { return parent !== undefined && node.kind === ts.SyntaxKind.Block && - isTryStatement(parent) && + utils.isTryStatement(parent) && parent.finallyBlock === node; } -function isTryStatement(node: ts.Node): node is ts.TryStatement { - return node.kind === ts.SyntaxKind.TryStatement; -} - function isReturnsOrThrowsBoundary(scope: IFinallyScope) { return scope.isReturnsThrowsBoundary; } diff --git a/src/rules/typedefRule.ts b/src/rules/typedefRule.ts index 0e5326faf17..81a41e133b7 100644 --- a/src/rules/typedefRule.ts +++ b/src/rules/typedefRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -139,7 +140,7 @@ class TypedefWalker extends Lint.RuleWalker { let optionName: string | null = null; if (isArrowFunction && isTypedPropertyDeclaration(node.parent.parent)) { // leave optionName as null and don't perform check - } else if (isArrowFunction && isPropertyDeclaration(node.parent.parent)) { + } else if (isArrowFunction && utils.isPropertyDeclaration(node.parent.parent)) { optionName = "member-variable-declaration"; } else if (isArrowFunction) { optionName = "arrow-parameter"; @@ -259,10 +260,6 @@ function getName(name?: ts.Node, prefix?: string, suffix?: string): string { return ns ? `${prefix || ""}${ns}${suffix || ""}` : ""; } -function isPropertyDeclaration(node: ts.Node): node is ts.PropertyDeclaration { - return node.kind === ts.SyntaxKind.PropertyDeclaration; -} - function isTypedPropertyDeclaration(node: ts.Node): boolean { - return isPropertyDeclaration(node) && node.type != null; + return utils.isPropertyDeclaration(node) && node.type != null; } diff --git a/src/rules/unifiedSignaturesRule.ts b/src/rules/unifiedSignaturesRule.ts index 8d844604ddf..b9dec69a282 100644 --- a/src/rules/unifiedSignaturesRule.ts +++ b/src/rules/unifiedSignaturesRule.ts @@ -15,12 +15,13 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; import { arraysAreEqual, Equal } from "../utils"; -import { getOverloadKey, isSignatureDeclaration } from "./adjacentOverloadSignaturesRule"; +import { getOverloadKey } from "./adjacentOverloadSignaturesRule"; export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ @@ -94,7 +95,7 @@ class Walker extends Lint.RuleWalker { private checkMembers(members: Array, typeParameters?: ts.TypeParameterDeclaration[]) { this.checkOverloads(members, getOverloadName, typeParameters); function getOverloadName(member: ts.TypeElement | ts.ClassElement) { - if (!isSignatureDeclaration(member) || (member as ts.MethodDeclaration).body) { + if (!utils.isSignatureDeclaration(member) || (member as ts.MethodDeclaration).body) { return undefined; } const key = getOverloadKey(member); diff --git a/src/rules/whitespaceRule.ts b/src/rules/whitespaceRule.ts index 711693cf97f..0df3e3ee239 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import * as utils from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; @@ -80,7 +81,7 @@ class WhitespaceWalker extends Lint.RuleWalker { super.visitSourceFile(node); let prevTokenShouldBeFollowedByWhitespace = false; - Lint.forEachToken(node, false, (_text, tokenKind, pos, parent) => { + utils.forEachTokenWithTrivia(node, (_text, tokenKind, range, parent) => { if (tokenKind === ts.SyntaxKind.WhitespaceTrivia || tokenKind === ts.SyntaxKind.NewLineTrivia || tokenKind === ts.SyntaxKind.EndOfFileToken) { @@ -88,7 +89,7 @@ class WhitespaceWalker extends Lint.RuleWalker { prevTokenShouldBeFollowedByWhitespace = false; return; } else if (prevTokenShouldBeFollowedByWhitespace) { - this.addMissingWhitespaceErrorAt(pos.tokenStart); + this.addMissingWhitespaceErrorAt(range.pos); prevTokenShouldBeFollowedByWhitespace = false; } // check for trailing space after the given tokens diff --git a/yarn.lock b/yarn.lock index fb1c7e06704..06b77d7154d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1130,6 +1130,10 @@ tslint@latest: resolve "^1.1.7" update-notifier "^1.0.2" +tsutils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.0.0.tgz#a1f87d1092179987d13a1a8406deaec28ac3a0ad" + type-detect@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" @@ -1138,9 +1142,9 @@ type-detect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" -typescript@2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.4.tgz#b53b69fb841126acb1dd4b397d21daba87572251" +typescript@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.6.tgz#40c7e6e9e5da7961b7718b55505f9cac9487a607" unique-string@^1.0.0: version "1.0.0"