From 7c73890fc6c592652db503e15585c1fdebf55198 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 4 Jan 2017 17:17:08 -0800 Subject: [PATCH 1/6] Set `--lib es6`, and use `Map`/`Set` methods where possible. --- package.json | 5 +- scripts/tsconfig.json | 3 +- src/enableDisableRules.ts | 7 +-- src/formatters/proseFormatter.ts | 11 ++--- src/language/languageServiceHost.ts | 23 ++++----- src/language/utils.ts | 12 +---- .../walker/skippableTokenAwareRuleWalker.ts | 13 +++-- src/rules/adjacentOverloadSignaturesRule.ts | 6 +-- src/rules/commentFormatRule.ts | 6 +-- src/rules/completedDocsRule.ts | 8 ++-- src/rules/jsdocFormatRule.ts | 6 +-- src/rules/noMagicNumbersRule.ts | 38 +++++++-------- src/rules/noTrailingWhitespaceRule.ts | 6 +-- src/rules/noUseBeforeDeclareRule.ts | 10 ++-- src/rules/preferConstRule.ts | 47 ++++++++++--------- src/rules/preferForOfRule.ts | 20 ++++---- src/rules/unifiedSignaturesRule.ts | 18 +++---- src/rules/whitespaceRule.ts | 5 +- src/test/parse.ts | 25 +++++----- src/tsconfig.json | 1 + test/tsconfig.json | 1 + 21 files changed, 127 insertions(+), 144 deletions(-) diff --git a/package.json b/package.json index 1a609cf95f8..fa04276cf5c 100644 --- a/package.json +++ b/package.json @@ -71,5 +71,8 @@ "tslint-test-config-non-relative": "file:test/external/tslint-test-config-non-relative", "typescript": "2.1.4" }, - "license": "Apache-2.0" + "license": "Apache-2.0", + "engines": { + "node": ">=4.2.6" + } } diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index a0ebb235e95..cdcf30b5634 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -6,6 +6,7 @@ "noUnusedParameters": true, "noUnusedLocals": true, "sourceMap": true, - "target": "es5" + "target": "es5", + "lib": ["es6", "dom"] } } diff --git a/src/enableDisableRules.ts b/src/enableDisableRules.ts index 5b20d65cbe6..04a5d3d2924 100644 --- a/src/enableDisableRules.ts +++ b/src/enableDisableRules.ts @@ -47,10 +47,11 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { scanAllTokens(scan, (scanner: ts.Scanner) => { const startPos = scanner.getStartPos(); - if (this.tokensToSkipStartEndMap[startPos] != null) { + const skip = this.getSkipEndFromStart(startPos); + if (skip !== undefined) { // tokens to skip are places where the scanner gets confused about what the token is, without the proper context // (specifically, regex, identifiers, and templates). So skip those tokens. - scanner.setTextPos(this.tokensToSkipStartEndMap[startPos]); + scanner.setTextPos(skip); return; } @@ -117,7 +118,7 @@ export class EnableDisableRulesWalker extends SkippableTokenAwareRuleWalker { rulesList = commentTextParts[1].split(/\s+/).slice(1); // remove empty items and potential comment end. - rulesList = rulesList.filter((item) => !!item && item.indexOf("*/") === -1); + rulesList = rulesList.filter((item) => !!item && !item.includes("*/")); // potentially there were no items, so default to `all`. rulesList = rulesList.length > 0 ? rulesList : ["all"]; diff --git a/src/formatters/proseFormatter.ts b/src/formatters/proseFormatter.ts index 7128b4ca357..997412c388f 100644 --- a/src/formatters/proseFormatter.ts +++ b/src/formatters/proseFormatter.ts @@ -36,17 +36,12 @@ export class Formatter extends AbstractFormatter { const fixLines: string[] = []; if (fixes) { - const perFileFixes: { [fileName: string]: number } = {}; + const perFileFixes = new Map(); for (const fix of fixes) { - if (perFileFixes[fix.getFileName()] == null) { - perFileFixes[fix.getFileName()] = 1; - } else { - perFileFixes[fix.getFileName()]++; - } + perFileFixes.set(fix.getFileName(), (perFileFixes.get(fix.getFileName()) || 0) + 1); } - Object.keys(perFileFixes).forEach((fixedFile: string) => { - const fixCount = perFileFixes[fixedFile]; + perFileFixes.forEach((fixCount, fixedFile) => { fixLines.push(`Fixed ${fixCount} error(s) in ${fixedFile}`); }); fixLines.push(""); // add a blank line between fixes and failures diff --git a/src/language/languageServiceHost.ts b/src/language/languageServiceHost.ts index 3f9a70f2d16..bab1df1c031 100644 --- a/src/language/languageServiceHost.ts +++ b/src/language/languageServiceHost.ts @@ -24,31 +24,32 @@ interface LanguageServiceEditableHost extends ts.LanguageServiceHost { } export function wrapProgram(program: ts.Program): ts.LanguageService { - const files: {[name: string]: string} = {}; - const fileVersions: {[name: string]: number} = {}; + const files = new Map(); // file name -> content + const fileVersions = new Map(); const host: LanguageServiceEditableHost = { getCompilationSettings: () => program.getCompilerOptions(), getCurrentDirectory: () => program.getCurrentDirectory(), getDefaultLibFileName: () => "lib.d.ts", getScriptFileNames: () => program.getSourceFiles().map((sf) => sf.fileName), getScriptSnapshot: (name: string) => { - if (files.hasOwnProperty(name)) { - return ts.ScriptSnapshot.fromString(files[name]); + const file = files.get(name); + if (file !== undefined) { + return ts.ScriptSnapshot.fromString(file); } if (!program.getSourceFile(name)) { return undefined; } return ts.ScriptSnapshot.fromString(program.getSourceFile(name).getFullText()); }, - getScriptVersion: (name: string) => fileVersions.hasOwnProperty(name) ? fileVersions[name] + "" : "1", + getScriptVersion: (name: string) => { + const version = fileVersions.get(name); + return version === undefined ? "1" : String(version); + }, log: () => { /* */ }, editFile(fileName: string, newContent: string) { - files[fileName] = newContent; - if (fileVersions.hasOwnProperty(fileName)) { - fileVersions[fileName]++; - } else { - fileVersions[fileName] = 0; - } + files.set(fileName, newContent); + const prevVersion = fileVersions.get(fileName); + fileVersions.set(fileName, prevVersion === undefined ? 0 : prevVersion + 1); }, }; const langSvc = ts.createLanguageService(host, ts.createDocumentRegistry()); diff --git a/src/language/utils.ts b/src/language/utils.ts index 52bbbb4dc14..b79bff5b45f 100644 --- a/src/language/utils.ts +++ b/src/language/utils.ts @@ -117,22 +117,12 @@ export function getBindingElementVariableDeclaration(node: ts.BindingElement): t return currentParent as ts.VariableDeclaration; } -/** Shim of Array.find */ -function find(a: T[], predicate: (value: T) => boolean): T | undefined { - for (const value of a) { - if (predicate(value)) { - return value; - } - } - return undefined; -} - /** * Finds a child of a given node with a given kind. * Note: This uses `node.getChildren()`, which does extra parsing work to include tokens. */ export function childOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined { - return find(node.getChildren(), (child) => child.kind === kind); + return node.getChildren().find((child) => child.kind === kind); } /** diff --git a/src/language/walker/skippableTokenAwareRuleWalker.ts b/src/language/walker/skippableTokenAwareRuleWalker.ts index e3a6e6857ce..0c2f2fc7c1a 100644 --- a/src/language/walker/skippableTokenAwareRuleWalker.ts +++ b/src/language/walker/skippableTokenAwareRuleWalker.ts @@ -21,11 +21,10 @@ import {IOptions} from "../rule/rule"; import {RuleWalker} from "./ruleWalker"; export class SkippableTokenAwareRuleWalker extends RuleWalker { - protected tokensToSkipStartEndMap: {[start: number]: number}; + private tokensToSkipStartEndMap = new Map(); constructor(sourceFile: ts.SourceFile, options: IOptions) { super(sourceFile, options); - this.tokensToSkipStartEndMap = {}; } protected visitRegularExpressionLiteral(node: ts.Node) { @@ -44,9 +43,15 @@ export class SkippableTokenAwareRuleWalker extends RuleWalker { } protected addTokenToSkipFromNode(node: ts.Node) { - if (node.getStart() < node.getEnd()) { + const start = node.getStart(); + const end = node.getEnd(); + if (start < end) { // only add to the map nodes whose end comes after their start, to prevent infinite loops - this.tokensToSkipStartEndMap[node.getStart()] = node.getEnd(); + this.tokensToSkipStartEndMap.set(start, end); } } + + protected getSkipEndFromStart(start: number): number | undefined { + return this.tokensToSkipStartEndMap.get(start); + } } diff --git a/src/rules/adjacentOverloadSignaturesRule.ts b/src/rules/adjacentOverloadSignaturesRule.ts index 72954eb0caf..eb896aefee6 100644 --- a/src/rules/adjacentOverloadSignaturesRule.ts +++ b/src/rules/adjacentOverloadSignaturesRule.ts @@ -90,15 +90,15 @@ class AdjacentOverloadSignaturesWalker extends Lint.RuleWalker { /** 'getOverloadName' may return undefined for nodes that cannot be overloads, e.g. a `const` declaration. */ private checkOverloadsAdjacent(overloads: T[], getOverload: (node: T) => Overload | undefined) { let lastKey: string | undefined = undefined; - const seen: { [key: string]: true } = Object.create(null); + const seen = new Set(); for (const node of overloads) { const overload = getOverload(node); if (overload) { const { name, key } = overload; - if (key in seen && lastKey !== key) { + if (seen.has(key) && lastKey !== key) { this.addFailureAtNode(node, Rule.FAILURE_STRING_FACTORY(name)); } - seen[key] = true; + seen.add(key); lastKey = key; } else { lastKey = undefined; diff --git a/src/rules/commentFormatRule.ts b/src/rules/commentFormatRule.ts index e65a16b3fc3..b058c185af3 100644 --- a/src/rules/commentFormatRule.ts +++ b/src/rules/commentFormatRule.ts @@ -64,11 +64,11 @@ class CommentWalker extends Lint.SkippableTokenAwareRuleWalker { public visitSourceFile(node: ts.SourceFile) { super.visitSourceFile(node); Lint.scanAllTokens(ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text), (scanner: ts.Scanner) => { - const startPos = scanner.getStartPos(); - if (this.tokensToSkipStartEndMap[startPos] != null) { + const skip = this.getSkipEndFromStart(scanner.getStartPos()); + if (skip !== undefined) { // tokens to skip are places where the scanner gets confused about what the token is, without the proper context // (specifically, regex, identifiers, and templates). So skip those tokens. - scanner.setTextPos(this.tokensToSkipStartEndMap[startPos]); + scanner.setTextPos(skip); return; } diff --git a/src/rules/completedDocsRule.ts b/src/rules/completedDocsRule.ts index a1bdcc667c6..3d4aa5db9c0 100644 --- a/src/rules/completedDocsRule.ts +++ b/src/rules/completedDocsRule.ts @@ -70,12 +70,10 @@ export class Rule extends Lint.Rules.TypedRule { } export class CompletedDocsWalker extends Lint.ProgramAwareRuleWalker { - private nodesToCheck: { [i: string]: boolean } = {}; + private nodesToCheck: Set; public setNodesToCheck(nodesToCheck: string[]): void { - for (const nodeType of nodesToCheck) { - this.nodesToCheck[nodeType] = true; - } + this.nodesToCheck = new Set(nodesToCheck); } public visitClassDeclaration(node: ts.ClassDeclaration): void { @@ -99,7 +97,7 @@ export class CompletedDocsWalker extends Lint.ProgramAwareRuleWalker { } private checkComments(node: ts.Declaration, nodeToCheck: string): void { - if (!this.nodesToCheck[nodeToCheck] || node.name === undefined) { + if (!this.nodesToCheck.has(nodeToCheck) || node.name === undefined) { return; } diff --git a/src/rules/jsdocFormatRule.ts b/src/rules/jsdocFormatRule.ts index 784b8923fc8..5322276b1ab 100644 --- a/src/rules/jsdocFormatRule.ts +++ b/src/rules/jsdocFormatRule.ts @@ -52,11 +52,11 @@ class JsdocWalker extends Lint.SkippableTokenAwareRuleWalker { public visitSourceFile(node: ts.SourceFile) { super.visitSourceFile(node); Lint.scanAllTokens(ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text), (scanner: ts.Scanner) => { - const startPos = scanner.getStartPos(); - if (this.tokensToSkipStartEndMap[startPos] != null) { + const skip = this.getSkipEndFromStart(scanner.getStartPos()); + if (skip !== undefined) { // tokens to skip are places where the scanner gets confused about what the token is, without the proper context // (specifically, regex, identifiers, and templates). So skip those tokens. - scanner.setTextPos(this.tokensToSkipStartEndMap[startPos]); + scanner.setTextPos(skip); return; } diff --git a/src/rules/noMagicNumbersRule.ts b/src/rules/noMagicNumbersRule.ts index 44dd8909c55..a92624519ab 100644 --- a/src/rules/noMagicNumbersRule.ts +++ b/src/rules/noMagicNumbersRule.ts @@ -47,17 +47,17 @@ export class Rule extends Lint.Rules.AbstractRule { public static FAILURE_STRING = "'magic numbers' are not allowed"; - public static ALLOWED_NODES = { - [ts.SyntaxKind.ExportAssignment]: true, - [ts.SyntaxKind.FirstAssignment]: true, - [ts.SyntaxKind.LastAssignment]: true, - [ts.SyntaxKind.PropertyAssignment]: true, - [ts.SyntaxKind.ShorthandPropertyAssignment]: true, - [ts.SyntaxKind.VariableDeclaration]: true, - [ts.SyntaxKind.VariableDeclarationList]: true, - [ts.SyntaxKind.EnumMember]: true, - [ts.SyntaxKind.PropertyDeclaration]: true, - }; + public static ALLOWED_NODES = new Set([ + ts.SyntaxKind.ExportAssignment, + ts.SyntaxKind.FirstAssignment, + ts.SyntaxKind.LastAssignment, + ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.ShorthandPropertyAssignment, + ts.SyntaxKind.VariableDeclaration, + ts.SyntaxKind.VariableDeclarationList, + ts.SyntaxKind.EnumMember, + ts.SyntaxKind.PropertyDeclaration, + ]); public static DEFAULT_ALLOWED = [ -1, 0, 1 ]; @@ -68,25 +68,21 @@ export class Rule extends Lint.Rules.AbstractRule { class NoMagicNumbersWalker extends Lint.RuleWalker { // lookup object for allowed magic numbers - private allowed: { [prop: string]: boolean } = {}; + private allowed: Set; constructor (sourceFile: ts.SourceFile, options: IOptions) { super(sourceFile, options); const configOptions = this.getOptions(); const allowedNumbers: number[] = configOptions.length > 0 ? configOptions : Rule.DEFAULT_ALLOWED; - - allowedNumbers.forEach((value) => { - this.allowed[value] = true; - }); + this.allowed = new Set(allowedNumbers.map(String)); } public visitNode(node: ts.Node) { const isUnary = this.isUnaryNumericExpression(node); - if (node.kind === ts.SyntaxKind.NumericLiteral && node.parent !== undefined && !Rule.ALLOWED_NODES[node.parent.kind] || isUnary) { - const text = node.getText(); - if (!this.allowed[text]) { - this.addFailureAtNode(node, Rule.FAILURE_STRING); - } + const isNumber = node.kind === ts.SyntaxKind.NumericLiteral && !Rule.ALLOWED_NODES.has(node.parent!.kind); + const isMagicNumber = (isNumber || isUnary) && !this.allowed.has(node.getText()); + if (isMagicNumber) { + this.addFailureAtNode(node, Rule.FAILURE_STRING); } if (!isUnary) { super.visitNode(node); diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index 2e25a31f96e..ddc15ef3e70 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -46,11 +46,11 @@ class NoTrailingWhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker { let lastSeenWasWhitespace = false; let lastSeenWhitespacePosition = 0; Lint.scanAllTokens(ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, node.text), (scanner: ts.Scanner) => { - const startPos = scanner.getStartPos(); - if (this.tokensToSkipStartEndMap[startPos] != null) { + const skip = this.getSkipEndFromStart(scanner.getStartPos()); + if (skip !== undefined) { // tokens to skip are places where the scanner gets confused about what the token is, without the proper context // (specifically, regex, identifiers, and templates). So skip those tokens. - scanner.setTextPos(this.tokensToSkipStartEndMap[startPos]); + scanner.setTextPos(skip); lastSeenWasWhitespace = false; return; } diff --git a/src/rules/noUseBeforeDeclareRule.ts b/src/rules/noUseBeforeDeclareRule.ts index 8a07b56a5b1..1dcdbbff6f7 100644 --- a/src/rules/noUseBeforeDeclareRule.ts +++ b/src/rules/noUseBeforeDeclareRule.ts @@ -43,9 +43,7 @@ export class Rule extends Lint.Rules.AbstractRule { } } -interface VisitedVariables { - [varName: string]: boolean; -} +type VisitedVariables = Set; class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { private importedPropertiesPositions: number[] = []; @@ -55,7 +53,7 @@ class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker(); } public visitBindingElement(node: ts.BindingElement) { @@ -112,11 +110,11 @@ class NoUseBeforeDeclareWalker extends Lint.ScopeAwareRuleWalker { } public onBlockScopeEnd() { - const seenLetStatements: { [startPosition: string]: boolean } = {}; + const seenLetStatements = new Set(); for (const usage of this.getCurrentBlockScope().getConstCandiates()) { let fix: Lint.Fix | undefined; - if (!usage.reassignedSibling && !seenLetStatements[usage.letStatement.getStart().toString()]) { + if (!usage.reassignedSibling && !seenLetStatements.has(usage.letStatement)) { // only fix if all variables in the `let` statement can use `const` const replacement = new Lint.Replacement(usage.letStatement.getStart(), "let".length, "const"); fix = new Lint.Fix(Rule.metadata.ruleName, [replacement]); - seenLetStatements[usage.letStatement.getStart().toString()] = true; + seenLetStatements.add(usage.letStatement); } this.addFailureAtNode(usage.identifier, Rule.FAILURE_STRING_FACTORY(usage.identifier.text), fix); } @@ -195,34 +195,34 @@ interface IConstCandidate { reassignedSibling: boolean; } +interface UsageInfo { + letStatement: ts.VariableDeclarationList; + identifier: ts.Identifier; + usageCount: number; +} + class ScopeInfo { public currentVariableDeclaration: ts.VariableDeclaration; - private identifierUsages: { - [varName: string]: { - letStatement: ts.VariableDeclarationList, - identifier: ts.Identifier, - usageCount: number, - }, - } = {}; + private identifierUsages = new Map(); // variable names grouped by common `let` statements - private sharedLetSets: {[letStartIndex: string]: string[]} = {}; + private sharedLetSets = new Map(); public addVariable(identifier: ts.Identifier, letStatement: ts.VariableDeclarationList) { - this.identifierUsages[identifier.text] = { letStatement, identifier, usageCount: 0 }; - const letSetKey = letStatement.getStart().toString(); - if (this.sharedLetSets[letSetKey] == null) { - this.sharedLetSets[letSetKey] = []; + this.identifierUsages.set(identifier.text, { letStatement, identifier, usageCount: 0 }); + let shared = this.sharedLetSets.get(letStatement); + if (shared === undefined) { + shared = []; + this.sharedLetSets.set(letStatement, shared); } - this.sharedLetSets[letSetKey].push(identifier.text); + shared.push(identifier.text); } public getConstCandiates() { const constCandidates: IConstCandidate[] = []; - for (const letSetKey of Object.keys(this.sharedLetSets)) { - const variableNames = this.sharedLetSets[letSetKey]; - const anyReassigned = variableNames.some((key) => this.identifierUsages[key].usageCount > 0); + this.sharedLetSets.forEach((variableNames) => { + const anyReassigned = variableNames.some((key) => this.identifierUsages.get(key)!.usageCount > 0); for (const variableName of variableNames) { - const usage = this.identifierUsages[variableName]; + const usage = this.identifierUsages.get(variableName)!; if (usage.usageCount === 0) { constCandidates.push({ identifier: usage.identifier, @@ -231,13 +231,14 @@ class ScopeInfo { }); } } - } + }); return constCandidates; } public incrementVariableUsage(varName: string) { - if (this.identifierUsages[varName] != null) { - this.identifierUsages[varName].usageCount++; + const usages = this.identifierUsages.get(varName); + if (usages !== undefined) { + usages.usageCount++; return true; } return false; diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 6d796e14f5f..6fb930f54ff 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -47,17 +47,15 @@ interface IIncrementorState { } // a map of incrementors and whether or not they are only used to index into an array reference in the for loop -interface IncrementorMap { - [name: string]: IIncrementorState; -} +type IncrementorMap = Map; class PreferForOfWalker extends Lint.BlockScopeAwareRuleWalker<{}, IncrementorMap> { public createScope() { - return {}; + return new Map(); } public createBlockScope() { - return {}; + return new Map(); } protected visitForStatement(node: ts.ForStatement) { @@ -69,31 +67,31 @@ class PreferForOfWalker extends Lint.BlockScopeAwareRuleWalker<{}, IncrementorMa indexVariableName = indexVariable.getText(); // store `for` loop state - currentBlockScope[indexVariableName] = { + currentBlockScope.set(indexVariableName, { arrayToken: arrayToken as ts.Identifier, forLoopEndPosition: node.incrementor.end + 1, onlyArrayReadAccess: true, - }; + }); } super.visitForStatement(node); if (indexVariableName != null) { - const incrementorState = currentBlockScope[indexVariableName]; + const incrementorState = currentBlockScope.get(indexVariableName)!; if (incrementorState.onlyArrayReadAccess) { this.addFailureFromStartToEnd(node.getStart(), incrementorState.forLoopEndPosition, Rule.FAILURE_STRING); } // remove current `for` loop state - delete currentBlockScope[indexVariableName]; + currentBlockScope.delete(indexVariableName); } } protected visitIdentifier(node: ts.Identifier) { - const incrementorScope = this.findBlockScope((scope) => scope[node.text] != null); + const incrementorScope = this.findBlockScope((scope) => scope.has(node.text)); if (incrementorScope != null) { - const incrementorState = incrementorScope[node.text]; + const incrementorState = incrementorScope.get(node.text); // check if the identifier is an iterator and is currently in the `for` loop body if (incrementorState != null && incrementorState.arrayToken != null && incrementorState.forLoopEndPosition < node.getStart()) { diff --git a/src/rules/unifiedSignaturesRule.ts b/src/rules/unifiedSignaturesRule.ts index 5e4a4762165..3bccd1ab63a 100644 --- a/src/rules/unifiedSignaturesRule.ts +++ b/src/rules/unifiedSignaturesRule.ts @@ -207,11 +207,11 @@ function getIsTypeParameter(typeParameters?: ts.TypeParameterDeclaration[]): IsT return () => false; } - const set: { [key: string]: true } = Object.create(null); + const set = new Set(); for (const t of typeParameters) { - set[t.getText()] = true; + set.add(t.getText()); } - return (typeName: string) => set[typeName]; + return (typeName: string) => set.has(typeName); } /** True if any of the outer type parameters are used in a signature. */ @@ -234,9 +234,7 @@ function signatureUsesTypeParameter(sig: ts.SignatureDeclaration, isTypeParamete * Does not rely on overloads being adjacent. This is similar to code in adjacentOverloadSignaturesRule.ts, but not the same. */ function collectOverloads(nodes: T[], getOverload: GetOverload): ts.SignatureDeclaration[][] { - const map: { [key: string]: ts.SignatureDeclaration[] } = Object.create(null); - // Array of values in the map. - const res: ts.SignatureDeclaration[][] = []; + const map = new Map(); for (const sig of nodes) { const overload = getOverload(sig); if (!overload) { @@ -244,16 +242,14 @@ function collectOverloads(nodes: T[], getOverload: GetOverload): ts.Signat } const { signature, key } = overload; - let overloads = map[key]; + const overloads = map.get(key); if (overloads) { overloads.push(signature); } else { - overloads = [signature]; - res.push(overloads); - map[key] = overloads; + map.set(key, [signature]); } } - return res; + return Array.from(map.values()); } function parametersAreEqual(a: ts.ParameterDeclaration, b: ts.ParameterDeclaration): boolean { diff --git a/src/rules/whitespaceRule.ts b/src/rules/whitespaceRule.ts index 57d980b6d42..241b2622bbf 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -91,10 +91,11 @@ class WhitespaceWalker extends Lint.SkippableTokenAwareRuleWalker { prevTokenShouldBeFollowedByWhitespace = false; } - if (this.tokensToSkipStartEndMap[startPos] != null) { + const skip = this.getSkipEndFromStart(startPos); + if (skip !== undefined) { // tokens to skip are places where the scanner gets confused about what the token is, without the proper context // (specifically, regex, identifiers, and templates). So skip those tokens. - scanner.setTextPos(this.tokensToSkipStartEndMap[startPos]); + scanner.setTextPos(skip); return; } diff --git a/src/test/parse.ts b/src/test/parse.ts index 0525707bd4c..5bf1272bc37 100644 --- a/src/test/parse.ts +++ b/src/test/parse.ts @@ -51,15 +51,20 @@ export function parseErrorsFromMarkup(text: string): LintError[] { } const messageSubstitutionLines = lines.filter((l) => l instanceof MessageSubstitutionLine) as MessageSubstitutionLine[]; - const messageSubstitutions: { [key: string]: string } = {}; - for (const line of messageSubstitutionLines) { - messageSubstitutions[line.key] = line.message; - } + const messageSubstitutions = new Map(messageSubstitutionLines.map(({ key, message }) => + [key, message] as [string, string])); // errorLineForCodeLine[5] contains all the ErrorLine objects associated with the 5th line of code, for example const errorLinesForCodeLines = createCodeLineNoToErrorsMap(lines); const lintErrors: LintError[] = []; + function addError(errorLine: EndErrorLine, errorStartPos: { line: number, col: number }, lineNo: number) { + lintErrors.push({ + startPos: errorStartPos, + endPos: { line: lineNo, col: errorLine.endCol }, + message: messageSubstitutions.get(errorLine.message) || errorLine.message, + }); + } // for each line of code... errorLinesForCodeLines.forEach((errorLinesForLineOfCode, lineNo) => { @@ -70,11 +75,7 @@ export function parseErrorsFromMarkup(text: string): LintError[] { // if the error starts and ends on this line, add it now to list of errors if (errorLine instanceof EndErrorLine) { - lintErrors.push({ - startPos: errorStartPos, - endPos: { line: lineNo, col: errorLine.endCol }, - message: messageSubstitutions[errorLine.message] || errorLine.message, - }); + addError(errorLine, errorStartPos, lineNo); // if the error is the start of a multiline error } else if (errorLine instanceof MultilineErrorLine) { @@ -90,11 +91,7 @@ export function parseErrorsFromMarkup(text: string): LintError[] { // if end of multiline error, add it it list of errors if (nextErrorLine instanceof EndErrorLine) { - lintErrors.push({ - startPos: errorStartPos, - endPos: { line: nextLineNo, col: nextErrorLine.endCol }, - message: messageSubstitutions[nextErrorLine.message] || nextErrorLine.message, - }); + addError(nextErrorLine, errorStartPos, nextLineNo); break; } } diff --git a/src/tsconfig.json b/src/tsconfig.json index bba9bd7b433..77b8eb3033b 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -10,6 +10,7 @@ "declaration": true, "sourceMap": false, "target": "es5", + "lib": ["es6", "dom"], "outDir": "../lib" } } diff --git a/test/tsconfig.json b/test/tsconfig.json index de519b752ed..f3a2ab25212 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,6 +9,7 @@ "strictNullChecks": true, "sourceMap": true, "target": "es5", + "lib": ["es6", "dom"], "outDir": "../build" }, "include": [ From 5e72879fb2ad6261880a75922c8c6156b4085f7b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 4 Jan 2017 19:02:58 -0800 Subject: [PATCH 2/6] Remove underscore dependency; use own camelize --- package.json | 3 --- scripts/tsconfig.json | 2 +- src/formatterLoader.ts | 2 +- src/formatters/msbuildFormatter.ts | 6 ++---- src/ruleLoader.ts | 5 ++--- src/tsconfig.json | 2 +- src/utils.ts | 8 ++++++++ test/tsconfig.json | 2 +- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index fa04276cf5c..f31ef4be485 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "glob": "^7.1.1", "optimist": "~0.6.0", "resolve": "^1.1.7", - "underscore.string": "^3.3.4", "update-notifier": "^1.0.2" }, "peerDependencies": { @@ -60,8 +59,6 @@ "@types/node": "^6.0.56", "@types/optimist": "0.0.29", "@types/resolve": "0.0.4", - "@types/underscore": "^1.7.36", - "@types/underscore.string": "0.0.30", "chai": "^3.5.0", "js-yaml": "^3.7.0", "mocha": "^3.2.0", diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index cdcf30b5634..982fca24da4 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -7,6 +7,6 @@ "noUnusedLocals": true, "sourceMap": true, "target": "es5", - "lib": ["es6", "dom"] + "lib": ["es6"] } } diff --git a/src/formatterLoader.ts b/src/formatterLoader.ts index 1e4256347da..32f7b09cdbc 100644 --- a/src/formatterLoader.ts +++ b/src/formatterLoader.ts @@ -17,7 +17,7 @@ import * as fs from "fs"; import * as path from "path"; -import {camelize} from "underscore.string"; +import {camelize} from "./utils"; const moduleDirectory = path.dirname(module.filename); const CORE_FORMATTERS_DIRECTORY = path.resolve(moduleDirectory, ".", "formatters"); diff --git a/src/formatters/msbuildFormatter.ts b/src/formatters/msbuildFormatter.ts index 40f92f80d83..8af372d9602 100644 --- a/src/formatters/msbuildFormatter.ts +++ b/src/formatters/msbuildFormatter.ts @@ -15,20 +15,18 @@ * limitations under the License. */ -import {camelize} from "underscore.string"; - import {AbstractFormatter} from "../language/formatter/abstractFormatter"; import {IFormatterMetadata} from "../language/formatter/formatter"; import {RuleFailure} from "../language/rule/rule"; -import * as Utils from "../utils"; +import {camelize, dedent} from "../utils"; export class Formatter extends AbstractFormatter { /* tslint:disable:object-literal-sort-keys */ public static metadata: IFormatterMetadata = { formatterName: "msbuild", description: "Formats errors for consumption by msbuild.", - descriptionDetails: Utils.dedent` + descriptionDetails: dedent` The output is compatible with both msbuild and Visual Studio. All failures have the 'warning' severity.`, sample: "myFile.ts(1,14): warning: Missing semicolon", diff --git a/src/ruleLoader.ts b/src/ruleLoader.ts index 2ba56032387..9ca7859370a 100644 --- a/src/ruleLoader.ts +++ b/src/ruleLoader.ts @@ -17,11 +17,10 @@ import * as fs from "fs"; import * as path from "path"; -import {camelize} from "underscore.string"; import {getRulesDirectories} from "./configuration"; import {IDisabledInterval, IRule} from "./language/rule/rule"; -import {dedent} from "./utils"; +import {camelize, dedent} from "./utils"; const moduleDirectory = path.dirname(module.filename); const CORE_RULES_DIRECTORY = path.resolve(moduleDirectory, ".", "rules"); @@ -151,7 +150,7 @@ function buildDisabledIntervalsFromSwitches(ruleSpecificList: IEnableDisablePosi while (i < ruleSpecificList.length) { const startPosition = ruleSpecificList[i].position; - // rule enabled state is always alternating therefore we can use position of next switch as end of disabled interval + // rule enabled state is always alternating therefore we can use position of next switch as end of disabled interval // set endPosition as Infinity in case when last switch for rule in a file is disabled const endPosition = ruleSpecificList[i + 1] ? ruleSpecificList[i + 1].position : Infinity; diff --git a/src/tsconfig.json b/src/tsconfig.json index 77b8eb3033b..b85f41cf5f2 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -10,7 +10,7 @@ "declaration": true, "sourceMap": false, "target": "es5", - "lib": ["es6", "dom"], + "lib": ["es6"], "outDir": "../lib" } } diff --git a/src/utils.ts b/src/utils.ts index 6dbf524e026..7460eb6464f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -39,6 +39,14 @@ export function objectify(arg: any): any { } } +/** + * Replace hyphens in a rule name by upper-casing the letter after them. + * E.g. "foo-bar" -> "fooBar" + */ +export function camelize(stringWithHyphens: string): string { + return stringWithHyphens.replace(/-(.)/g, (_, nextLetter) => nextLetter.toUpperCase()); +} + /** * Removes leading indents from a template string without removing all leading whitespace */ diff --git a/test/tsconfig.json b/test/tsconfig.json index f3a2ab25212..685d2f2fb98 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,7 +9,7 @@ "strictNullChecks": true, "sourceMap": true, "target": "es5", - "lib": ["es6", "dom"], + "lib": ["es6"], "outDir": "../build" }, "include": [ From b9066b69eaa5afccecd989f24c9bc73e451e84f6 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 5 Jan 2017 17:06:29 -0800 Subject: [PATCH 3/6] Fix createScope --- src/rules/preferForOfRule.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/rules/preferForOfRule.ts b/src/rules/preferForOfRule.ts index 4cb4b3c3799..398c9d3bee8 100644 --- a/src/rules/preferForOfRule.ts +++ b/src/rules/preferForOfRule.ts @@ -49,10 +49,8 @@ interface IIncrementorState { // a map of incrementors and whether or not they are only used to index into an array reference in the for loop type IncrementorMap = Map; -class PreferForOfWalker extends Lint.BlockScopeAwareRuleWalker<{}, IncrementorMap> { - public createScope() { - return new Map(); - } +class PreferForOfWalker extends Lint.BlockScopeAwareRuleWalker { + public createScope() {} // tslint:disable-line:no-empty public createBlockScope() { return new Map(); @@ -199,8 +197,6 @@ class PreferForOfWalker extends Lint.BlockScopeAwareRuleWalker<{}, IncrementorMa } } } - } else { - return false; } return false; } From 049db1ba3aaa3c8435e6a04ae06e502ef5a4a8d2 Mon Sep 17 00:00:00 2001 From: Noah Chen Date: Fri, 6 Jan 2017 22:44:44 -0800 Subject: [PATCH 4/6] Update noMagicNumbersRule.ts --- src/rules/noMagicNumbersRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/noMagicNumbersRule.ts b/src/rules/noMagicNumbersRule.ts index a92624519ab..2b6fd8d9758 100644 --- a/src/rules/noMagicNumbersRule.ts +++ b/src/rules/noMagicNumbersRule.ts @@ -69,7 +69,7 @@ export class Rule extends Lint.Rules.AbstractRule { class NoMagicNumbersWalker extends Lint.RuleWalker { // lookup object for allowed magic numbers private allowed: Set; - constructor (sourceFile: ts.SourceFile, options: IOptions) { + constructor(sourceFile: ts.SourceFile, options: IOptions) { super(sourceFile, options); const configOptions = this.getOptions(); From 2800aa4c3df60fb9993ea65d5942d9c72f5b0974 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Sat, 7 Jan 2017 08:16:11 -0800 Subject: [PATCH 5/6] Remove unnecessary constructor --- src/language/walker/skippableTokenAwareRuleWalker.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/language/walker/skippableTokenAwareRuleWalker.ts b/src/language/walker/skippableTokenAwareRuleWalker.ts index 0c2f2fc7c1a..5f1a3d28a7c 100644 --- a/src/language/walker/skippableTokenAwareRuleWalker.ts +++ b/src/language/walker/skippableTokenAwareRuleWalker.ts @@ -17,16 +17,11 @@ import * as ts from "typescript"; -import {IOptions} from "../rule/rule"; import {RuleWalker} from "./ruleWalker"; export class SkippableTokenAwareRuleWalker extends RuleWalker { private tokensToSkipStartEndMap = new Map(); - constructor(sourceFile: ts.SourceFile, options: IOptions) { - super(sourceFile, options); - } - protected visitRegularExpressionLiteral(node: ts.Node) { this.addTokenToSkipFromNode(node); super.visitRegularExpressionLiteral(node); From 9e1e9ada1a2b309a1821d2e62cc844ca687bd22c Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Sat, 7 Jan 2017 08:47:30 -0800 Subject: [PATCH 6/6] Fix merge: re-implement #2004 --- docs/_data/rules.json | 17 +++++++++--- docs/rules/no-unused-expression/index.html | 30 +++++++++++++++++++--- src/rules/noMagicNumbersRule.ts | 1 + 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/docs/_data/rules.json b/docs/_data/rules.json index 5d5528fa11c..fa358c66214 100644 --- a/docs/_data/rules.json +++ b/docs/_data/rules.json @@ -951,10 +951,21 @@ "description": "Disallows unused expression statements.", "descriptionDetails": "\nUnused expressions are expression statements which are not assignments or function calls\n(and thus usually no-ops).", "rationale": "\nDetects potential errors where an assignment or function call was intended.", - "optionsDescription": "Not configurable.", - "options": null, + "optionsDescription": "\nOne argument may be optionally provided:\n\n* `allow-fast-null-checks` allows to use logical operators to perform fast null checks and perform\nmethod or function calls for side effects (e.g. `e && e.preventDefault()`).", + "options": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "allow-fast-null-checks" + ] + }, + "minLength": 0, + "maxLength": 1 + }, "optionExamples": [ - "true" + "true", + "[true, \"allow-fast-null-checks\"]" ], "type": "functionality", "typescriptOnly": false diff --git a/docs/rules/no-unused-expression/index.html b/docs/rules/no-unused-expression/index.html index 82075de330a..6856223df77 100644 --- a/docs/rules/no-unused-expression/index.html +++ b/docs/rules/no-unused-expression/index.html @@ -8,13 +8,37 @@ rationale: |- Detects potential errors where an assignment or function call was intended. -optionsDescription: Not configurable. -options: null +optionsDescription: |- + + One argument may be optionally provided: + + * `allow-fast-null-checks` allows to use logical operators to perform fast null checks and perform + method or function calls for side effects (e.g. `e && e.preventDefault()`). +options: + type: array + items: + type: string + enum: + - allow-fast-null-checks + minLength: 0 + maxLength: 1 optionExamples: - 'true' + - '[true, "allow-fast-null-checks"]' type: functionality typescriptOnly: false layout: rule title: 'Rule: no-unused-expression' -optionsJSON: 'null' +optionsJSON: |- + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "allow-fast-null-checks" + ] + }, + "minLength": 0, + "maxLength": 1 + } --- \ No newline at end of file diff --git a/src/rules/noMagicNumbersRule.ts b/src/rules/noMagicNumbersRule.ts index 2b6fd8d9758..26d9b07a679 100644 --- a/src/rules/noMagicNumbersRule.ts +++ b/src/rules/noMagicNumbersRule.ts @@ -57,6 +57,7 @@ export class Rule extends Lint.Rules.AbstractRule { ts.SyntaxKind.VariableDeclarationList, ts.SyntaxKind.EnumMember, ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.Parameter, ]); public static DEFAULT_ALLOWED = [ -1, 0, 1 ];