From 09489852b8b5a7343b490fcd41723f8212ecf314 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Sat, 25 Mar 2017 09:10:15 -0700 Subject: [PATCH] [Breaking change] Remove `createFix`, and just use the Replacement(s) as the fix. (Minimal change) --- docs/_data/formatters.json | 2 +- docs/formatters/json/index.html | 11 ++---- src/formatters/jsonFormatter.ts | 11 ++---- src/language/rule/rule.ts | 37 ++++++------------- src/language/walker/ruleWalker.ts | 4 -- src/language/walker/walkContext.ts | 6 +-- src/linter.ts | 4 +- src/rules/alignRule.ts | 4 +- src/rules/arrayTypeRule.ts | 10 ++--- src/rules/arrowParensRule.ts | 8 ++-- src/rules/arrowReturnShorthandRule.ts | 4 +- src/rules/eoflineRule.ts | 4 +- src/rules/linebreakStyleRule.ts | 8 +--- src/rules/memberOrderingRule.ts | 20 +--------- src/rules/noAngleBracketTypeAssertionRule.ts | 4 +- src/rules/noAnyRule.ts | 4 +- src/rules/noBooleanLiteralCompareRule.ts | 2 +- src/rules/noConsecutiveBlankLinesRule.ts | 4 +- src/rules/noInferrableTypesRule.ts | 2 +- src/rules/noStringThrowRule.ts | 7 ++-- src/rules/noTrailingWhitespaceRule.ts | 4 +- src/rules/noUnnecessaryCallbackWrapperRule.ts | 5 ++- src/rules/noUnnecessaryInitializerRule.ts | 4 +- src/rules/noUnnecessaryQualifierRule.ts | 2 +- src/rules/noUnusedVariableRule.ts | 8 ++-- src/rules/noVarKeywordRule.ts | 5 +-- src/rules/objectLiteralKeyQuotesRule.ts | 4 +- src/rules/objectLiteralShorthandRule.ts | 4 +- src/rules/orderedImportsRule.ts | 8 ++-- src/rules/preferConstRule.ts | 4 +- src/rules/preferMethodSignatureRule.ts | 5 ++- src/rules/quotemarkRule.ts | 4 +- src/rules/semicolonRule.ts | 12 ++---- src/rules/spaceBeforeFunctionParenRule.ts | 6 +-- src/rules/trailingCommaRule.ts | 8 +--- src/rules/whitespaceRule.ts | 2 +- src/test.ts | 7 ++-- src/utils.ts | 21 +++++++++++ test/formatters/jsonFormatterTests.ts | 17 +++------ 39 files changed, 114 insertions(+), 172 deletions(-) diff --git a/docs/_data/formatters.json b/docs/_data/formatters.json index bb43d723422..55b689c0f51 100644 --- a/docs/_data/formatters.json +++ b/docs/_data/formatters.json @@ -22,7 +22,7 @@ { "formatterName": "json", "description": "Formats errors as simple JSON.", - "sample": "\n[\n {\n \"endPosition\": {\n \"character\": 13,\n \"line\": 0,\n \"position\": 13\n },\n \"failure\": \"Missing semicolon\",\n \"fix\": {\n \"innerRuleName\": \"semicolon\",\n \"innerReplacements\": [\n {\n \"innerStart\": 13,\n \"innerLength\": 0,\n \"innerText\": \";\"\n }\n ]\n },\n \"name\": \"myFile.ts\",\n \"ruleName\": \"semicolon\",\n \"startPosition\": {\n \"character\": 13,\n \"line\": 0,\n \"position\": 13\n }\n }\n]", + "sample": "\n[\n {\n \"endPosition\": {\n \"character\": 13,\n \"line\": 0,\n \"position\": 13\n },\n \"failure\": \"Missing semicolon\",\n \"fix\": {\n \"innerStart\": 13,\n \"innerLength\": 0,\n \"innerText\": \";\"\n },\n \"name\": \"myFile.ts\",\n \"ruleName\": \"semicolon\",\n \"startPosition\": {\n \"character\": 13,\n \"line\": 0,\n \"position\": 13\n }\n }\n]", "consumer": "machine" }, { diff --git a/docs/formatters/json/index.html b/docs/formatters/json/index.html index be794e27a62..7b14554cd02 100644 --- a/docs/formatters/json/index.html +++ b/docs/formatters/json/index.html @@ -12,14 +12,9 @@ }, "failure": "Missing semicolon", "fix": { - "innerRuleName": "semicolon", - "innerReplacements": [ - { - "innerStart": 13, - "innerLength": 0, - "innerText": ";" - } - ] + "innerStart": 13, + "innerLength": 0, + "innerText": ";" }, "name": "myFile.ts", "ruleName": "semicolon", diff --git a/src/formatters/jsonFormatter.ts b/src/formatters/jsonFormatter.ts index 917ffd8a2f5..28545d3dccc 100644 --- a/src/formatters/jsonFormatter.ts +++ b/src/formatters/jsonFormatter.ts @@ -36,14 +36,9 @@ export class Formatter extends AbstractFormatter { }, "failure": "Missing semicolon", "fix": { - "innerRuleName": "semicolon", - "innerReplacements": [ - { - "innerStart": 13, - "innerLength": 0, - "innerText": ";" - } - ] + "innerStart": 13, + "innerLength": 0, + "innerText": ";" }, "name": "myFile.ts", "ruleName": "semicolon", diff --git a/src/language/rule/rule.ts b/src/language/rule/rule.ts index 1e841769cad..bfcefa49124 100644 --- a/src/language/rule/rule.ts +++ b/src/language/rule/rule.ts @@ -17,6 +17,7 @@ import * as ts from "typescript"; +import {arrayify, flatMap} from "../../utils"; import {IWalker} from "../walker"; export interface IRuleMetadata { @@ -132,12 +133,20 @@ export function isTypedRule(rule: IRule): rule is ITypedRule { } export class Replacement { + public static applyFixes(content: string, fixes: Fix[]): string { + return this.applyAll(content, flatMap(fixes, arrayify)); + } + public static applyAll(content: string, replacements: Replacement[]) { // sort in reverse so that diffs are properly applied replacements.sort((a, b) => b.end - a.end); return replacements.reduce((text, r) => r.apply(text), content); } + public static replaceNode(node: ts.Node, text: string, sourceFile?: ts.SourceFile): Replacement { + return this.replaceFromTo(node.getStart(sourceFile), node.getEnd(), text); + } + public static replaceFromTo(start: number, end: number, text: string) { return new Replacement(start, end - start, text); } @@ -178,32 +187,6 @@ export class Replacement { } } -export class Fix { - public static applyAll(content: string, fixes: Fix[]) { - // accumulate replacements - let replacements: Replacement[] = []; - for (const fix of fixes) { - replacements = replacements.concat(fix.replacements); - } - return Replacement.applyAll(content, replacements); - } - - constructor(private innerRuleName: string, private innerReplacements: Replacement[]) { - } - - get ruleName() { - return this.innerRuleName; - } - - get replacements() { - return this.innerReplacements; - } - - public apply(content: string) { - return Replacement.applyAll(content, this.innerReplacements); - } -} - export class RuleFailurePosition { constructor(private position: number, private lineAndCharacter: ts.LineAndCharacter) { } @@ -234,6 +217,8 @@ export class RuleFailurePosition { } } +export type Fix = Replacement | Replacement[]; + export class RuleFailure { private fileName: string; private startPosition: RuleFailurePosition; diff --git a/src/language/walker/ruleWalker.ts b/src/language/walker/ruleWalker.ts index c686020faf9..a7817d5cfe5 100644 --- a/src/language/walker/ruleWalker.ts +++ b/src/language/walker/ruleWalker.ts @@ -110,8 +110,4 @@ export class RuleWalker extends SyntaxWalker implements IWalker { public getRuleName(): string { return this.ruleName; } - - public createFix(...replacements: Replacement[]): Fix { - return new Fix(this.ruleName, replacements); - } } diff --git a/src/language/walker/walkContext.ts b/src/language/walker/walkContext.ts index f93b480c192..838e55363cc 100644 --- a/src/language/walker/walkContext.ts +++ b/src/language/walker/walkContext.ts @@ -17,7 +17,7 @@ import * as ts from "typescript"; -import { Fix, Replacement, RuleFailure } from "../rule/rule"; +import { Fix, RuleFailure } from "../rule/rule"; export class WalkContext { public readonly failures: RuleFailure[] = []; @@ -40,8 +40,4 @@ export class WalkContext { public addFailureAtNode(node: ts.Node, failure: string, fix?: Fix) { this.addFailure(node.getStart(this.sourceFile), node.getEnd(), failure, fix); } - - public createFix(...replacements: Replacement[]) { - return new Fix(this.ruleName, replacements); - } } diff --git a/src/linter.ts b/src/linter.ts index 15ba445112e..8b27fd8e643 100644 --- a/src/linter.ts +++ b/src/linter.ts @@ -34,7 +34,7 @@ import { isError, showWarningOnce } from "./error"; import { findFormatter } from "./formatterLoader"; import { ILinterOptions, LintResult } from "./index"; import { IFormatter } from "./language/formatter/formatter"; -import { Fix, IRule, isTypedRule, RuleFailure, RuleSeverity } from "./language/rule/rule"; +import { Fix, IRule, isTypedRule, Replacement, RuleFailure, RuleSeverity } from "./language/rule/rule"; import * as utils from "./language/utils"; import { loadRules } from "./ruleLoader"; import { arrayify, dedent } from "./utils"; @@ -108,7 +108,7 @@ class Linter { source = fs.readFileSync(fileName, { encoding: "utf-8" }); if (fixes.length > 0) { this.fixes = this.fixes.concat(ruleFailures); - source = Fix.applyAll(source, fixes); + source = Replacement.applyFixes(source, fixes); fs.writeFileSync(fileName, source, { encoding: "utf-8" }); // reload AST if file is modified diff --git a/src/rules/alignRule.ts b/src/rules/alignRule.ts index f3efe0d9c94..b19a56ab16e 100644 --- a/src/rules/alignRule.ts +++ b/src/rules/alignRule.ts @@ -117,10 +117,10 @@ class AlignWalker extends Lint.AbstractWalker { const diff = alignToColumn - pos.character; let fix: Lint.Fix | undefined; if (0 < diff) { - fix = this.createFix(Lint.Replacement.appendText(start, " ".repeat(diff))); + fix = Lint.Replacement.appendText(start, " ".repeat(diff)); } else if (node.pos <= start + diff && /^\s+$/.test(sourceFile.text.substring(start + diff, start))) { // only delete text if there is only whitespace - fix = this.createFix(Lint.Replacement.deleteText(start + diff, -diff)); + fix = Lint.Replacement.deleteText(start + diff, -diff); } this.addFailure(start, node.end, kind + Rule.FAILURE_STRING_SUFFIX, fix); } diff --git a/src/rules/arrayTypeRule.ts b/src/rules/arrayTypeRule.ts index 40841ba6d05..18191930bd7 100644 --- a/src/rules/arrayTypeRule.ts +++ b/src/rules/arrayTypeRule.ts @@ -65,11 +65,11 @@ class ArrayTypeWalker extends Lint.RuleWalker { // Add a space if the type is preceded by 'as' and the node has no leading whitespace const space = !parens && node.parent!.kind === ts.SyntaxKind.AsExpression && node.getStart() === node.getFullStart() ? " " : ""; - const fix = this.createFix( + const fix = [ this.createReplacement(typeName.getStart(), parens, space + "Array<"), // Delete the square brackets and replace with an angle bracket this.createReplacement(typeName.getEnd() - parens, node.getEnd() - typeName.getEnd() + parens, ">"), - ); + ]; this.addFailureAtNode(node, failureString, fix); } @@ -83,7 +83,7 @@ class ArrayTypeWalker extends Lint.RuleWalker { const typeArgs = node.typeArguments; if (!typeArgs || typeArgs.length === 0) { // Create an 'any' array - const fix = this.createFix(this.createReplacement(node.getStart(), node.getWidth(), "any[]")); + const fix = this.createReplacement(node.getStart(), node.getWidth(), "any[]"); this.addFailureAtNode(node, failureString, fix); } else if (typeArgs && typeArgs.length === 1 && (!this.hasOption(OPTION_ARRAY_SIMPLE) || this.isSimpleType(typeArgs[0]))) { const type = typeArgs[0]; @@ -91,12 +91,12 @@ class ArrayTypeWalker extends Lint.RuleWalker { const typeEnd = type.getEnd(); const parens = type.kind === ts.SyntaxKind.UnionType || type.kind === ts.SyntaxKind.FunctionType || type.kind === ts.SyntaxKind.IntersectionType; - const fix = this.createFix( + const fix = [ // Delete Array and the first angle bracket this.createReplacement(node.getStart(), typeStart - node.getStart(), parens ? "(" : ""), // Delete the last angle bracket and replace with square brackets this.createReplacement(typeEnd, node.getEnd() - typeEnd, (parens ? ")" : "") + "[]"), - ); + ]; this.addFailureAtNode(node, failureString, fix); } } diff --git a/src/rules/arrowParensRule.ts b/src/rules/arrowParensRule.ts index cb68c2ff616..cc9c30f810a 100644 --- a/src/rules/arrowParensRule.ts +++ b/src/rules/arrowParensRule.ts @@ -65,17 +65,17 @@ function walk(ctx: Lint.WalkContext) { const parameter = node.parameters[0]; const start = parameter.getStart(ctx.sourceFile); const end = parameter.end; - ctx.addFailure(start, end, Rule.FAILURE_STRING_MISSING, ctx.createFix( + ctx.addFailure(start, end, Rule.FAILURE_STRING_MISSING, [ Lint.Replacement.appendText(start, "("), Lint.Replacement.appendText(end, ")"), - )); + ]); } } else if (ctx.options.banSingleArgParens) { const closeParen = getChildOfKind(node, ts.SyntaxKind.CloseParenToken)!; - ctx.addFailureAtNode(node.parameters[0], Rule.FAILURE_STRING_EXISTS, ctx.createFix( + ctx.addFailureAtNode(node.parameters[0], Rule.FAILURE_STRING_EXISTS, [ Lint.Replacement.deleteText(openParen.end - 1, 1), Lint.Replacement.deleteText(closeParen.end - 1, 1), - )); + ]); } } return ts.forEachChild(node, cb); diff --git a/src/rules/arrowReturnShorthandRule.ts b/src/rules/arrowReturnShorthandRule.ts index 4a765d5fe25..a4b9ce6823d 100644 --- a/src/rules/arrowReturnShorthandRule.ts +++ b/src/rules/arrowReturnShorthandRule.ts @@ -78,7 +78,7 @@ class Walker extends Lint.RuleWalker { const anyComments = hasComments(arrow) || hasComments(openBrace) || hasComments(statement) || hasComments(returnKeyword) || hasComments(expr) || (semicolon && hasComments(semicolon)) || hasComments(closeBrace); - return anyComments ? undefined : this.createFix( + return anyComments ? undefined : [ // Object literal must be wrapped in `()` ...(expr.kind === ts.SyntaxKind.ObjectLiteralExpression ? [ this.appendText(expr.getStart(), "("), @@ -90,7 +90,7 @@ class Walker extends Lint.RuleWalker { this.deleteFromTo(statement.getStart(), expr.getStart()), // " }" (may include semicolon) this.deleteFromTo(expr.end, closeBrace.end), - ); + ]; function hasComments(node: ts.Node): boolean { return ts.getTrailingCommentRanges(text, node.getEnd()) !== undefined; diff --git a/src/rules/eoflineRule.ts b/src/rules/eoflineRule.ts index fdd1fe13bd5..4543d7c4c64 100644 --- a/src/rules/eoflineRule.ts +++ b/src/rules/eoflineRule.ts @@ -45,9 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { let fix: Lint.Fix | undefined; const lines = sourceFile.getLineStarts(); if (lines.length > 1) { - fix = new Lint.Fix(this.ruleName, [ - Lint.Replacement.appendText(length, sourceFile.text[lines[1] - 2] === "\r" ? "\r\n" : "\n"), - ]); + fix = Lint.Replacement.appendText(length, sourceFile.text[lines[1] - 2] === "\r" ? "\r\n" : "\n"); } return this.filterFailures([ diff --git a/src/rules/linebreakStyleRule.ts b/src/rules/linebreakStyleRule.ts index c73c9556103..cc49eb9e504 100644 --- a/src/rules/linebreakStyleRule.ts +++ b/src/rules/linebreakStyleRule.ts @@ -59,14 +59,10 @@ function walk(ctx: Lint.WalkContext) { const lineEnd = lineStarts[i] - 1; if (sourceText[lineEnd - 1] === "\r") { if (!expectedCr) { - ctx.addFailure(lineStarts[i - 1], lineEnd - 1, Rule.FAILURE_LF, ctx.createFix( - Lint.Replacement.deleteText(lineEnd - 1, 1), - )); + ctx.addFailure(lineStarts[i - 1], lineEnd - 1, Rule.FAILURE_LF, Lint.Replacement.deleteText(lineEnd - 1, 1)); } } else if (expectedCr) { - ctx.addFailure(lineStarts[i - 1], lineEnd, Rule.FAILURE_CRLF, ctx.createFix( - Lint.Replacement.appendText(lineEnd, "\r"), - )); + ctx.addFailure(lineStarts[i - 1], lineEnd, Rule.FAILURE_CRLF, Lint.Replacement.appendText(lineEnd, "\r")); } } } diff --git a/src/rules/memberOrderingRule.ts b/src/rules/memberOrderingRule.ts index b09998fb2f2..ea085970748 100644 --- a/src/rules/memberOrderingRule.ts +++ b/src/rules/memberOrderingRule.ts @@ -17,6 +17,7 @@ import * as ts from "typescript"; import * as Lint from "../index"; +import {flatMap, mapDefined} from "../utils"; const OPTION_ORDER = "order"; const OPTION_ALPHABETIZE = "alphabetize"; @@ -478,22 +479,3 @@ function nameString(name: ts.PropertyName): string { return ""; } } - -function mapDefined(inputs: T[], getOutput: (input: T) => U | undefined): U[] { - const out = []; - for (const input of inputs) { - const output = getOutput(input); - if (output !== undefined) { - out.push(output); - } - } - return out; -} - -function flatMap(inputs: T[], getOutputs: (input: T) => U[]): U[] { - const out = []; - for (const input of inputs) { - out.push(...getOutputs(input)); - } - return out; -} diff --git a/src/rules/noAngleBracketTypeAssertionRule.ts b/src/rules/noAngleBracketTypeAssertionRule.ts index 282c5b2d63d..2db37b0e281 100644 --- a/src/rules/noAngleBracketTypeAssertionRule.ts +++ b/src/rules/noAngleBracketTypeAssertionRule.ts @@ -49,10 +49,10 @@ function walk(ctx: Lint.WalkContext) { return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (isTypeAssertion(node)) { const start = node.getStart(ctx.sourceFile); - ctx.addFailure(start, node.end, Rule.FAILURE_STRING, ctx.createFix( + ctx.addFailure(start, node.end, Rule.FAILURE_STRING, [ Lint.Replacement.appendText(node.end, ` as ${ node.type.getText(ctx.sourceFile) }`), Lint.Replacement.deleteFromTo(start, node.expression.getStart(ctx.sourceFile)), - )); + ]); } return ts.forEachChild(node, cb); }); diff --git a/src/rules/noAnyRule.ts b/src/rules/noAnyRule.ts index cf25a6d780b..9924ebd3bee 100644 --- a/src/rules/noAnyRule.ts +++ b/src/rules/noAnyRule.ts @@ -47,9 +47,7 @@ function walk(ctx: Lint.WalkContext) { return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (node.kind === ts.SyntaxKind.AnyKeyword) { const start = node.end - 3; - return ctx.addFailure(start, node.end, Rule.FAILURE_STRING, ctx.createFix( - new Lint.Replacement(start, 3, "{}"), - )); + return ctx.addFailure(start, node.end, Rule.FAILURE_STRING, new Lint.Replacement(start, 3, "{}")); } return ts.forEachChild(node, cb); }); diff --git a/src/rules/noBooleanLiteralCompareRule.ts b/src/rules/noBooleanLiteralCompareRule.ts index 73df31152ab..51f78022896 100644 --- a/src/rules/noBooleanLiteralCompareRule.ts +++ b/src/rules/noBooleanLiteralCompareRule.ts @@ -74,7 +74,7 @@ class Walker extends Lint.ProgramAwareRuleWalker { } } - this.addFailureAtNode(expression, Rule.FAILURE_STRING(negate), this.createFix(...replacements)); + this.addFailureAtNode(expression, Rule.FAILURE_STRING(negate), replacements); } } diff --git a/src/rules/noConsecutiveBlankLinesRule.ts b/src/rules/noConsecutiveBlankLinesRule.ts index f6c5069ee66..c49c28d1bf6 100644 --- a/src/rules/noConsecutiveBlankLinesRule.ts +++ b/src/rules/noConsecutiveBlankLinesRule.ts @@ -90,14 +90,14 @@ function walk(ctx: Lint.WalkContext) { const templateRanges = getTemplateRanges(ctx.sourceFile); for (const possibleFailure of possibleFailures) { if (!templateRanges.some((template) => template.pos < possibleFailure.pos && possibleFailure.pos < template.end)) { - ctx.addFailureAt(possibleFailure.pos, 1, failureString, ctx.createFix( + ctx.addFailureAt(possibleFailure.pos, 1, failureString, [ Lint.Replacement.deleteFromTo( // special handling for fixing blank lines at the end of the file // to fix this we need to cut off the line break of the last allowed blank line, too possibleFailure.end === sourceText.length ? getStartOfLineBreak(sourceText, possibleFailure.pos) : possibleFailure.pos, possibleFailure.end, ), - )); + ]); } } } diff --git a/src/rules/noInferrableTypesRule.ts b/src/rules/noInferrableTypesRule.ts index f3eb9c059ff..8cd14f6cf25 100644 --- a/src/rules/noInferrableTypesRule.ts +++ b/src/rules/noInferrableTypesRule.ts @@ -131,7 +131,7 @@ class NoInferrableTypesWalker extends Lint.AbstractWalker { if (failure != null) { this.addFailureAtNode(node.type, Rule.FAILURE_STRING_FACTORY(failure), - this.createFix(Lint.Replacement.deleteFromTo(node.name.end, node.type.end)), + Lint.Replacement.deleteFromTo(node.name.end, node.type.end), ); } } diff --git a/src/rules/noStringThrowRule.ts b/src/rules/noStringThrowRule.ts index 2f8e1f42005..9a8287ffef6 100644 --- a/src/rules/noStringThrowRule.ts +++ b/src/rules/noStringThrowRule.ts @@ -45,9 +45,10 @@ class Walker extends Lint.RuleWalker { public visitThrowStatement(node: ts.ThrowStatement) { const {expression} = node; if (this.stringConcatRecursive(expression)) { - const fix = this.createFix(this.createReplacement(expression.getStart(), - expression.getEnd() - expression.getStart(), - `new Error(${expression.getText()})`)); + const fix = this.createReplacement( + expression.getStart(), + expression.getEnd() - expression.getStart(), + `new Error(${expression.getText()})`); this.addFailure(this.createFailure( node.getStart(), node.getWidth(), Rule.FAILURE_STRING, fix)); } diff --git a/src/rules/noTrailingWhitespaceRule.ts b/src/rules/noTrailingWhitespaceRule.ts index 2a4f52f3583..ed474c33f88 100644 --- a/src/rules/noTrailingWhitespaceRule.ts +++ b/src/rules/noTrailingWhitespaceRule.ts @@ -100,9 +100,9 @@ function walk(ctx: Lint.WalkContext) { } for (const possibleFailure of possibleFailures) { if (!excludedRanges.some((range) => range.pos < possibleFailure.pos && possibleFailure.pos < range.end)) { - ctx.addFailure(possibleFailure.pos, possibleFailure.end, Rule.FAILURE_STRING, ctx.createFix( + ctx.addFailure(possibleFailure.pos, possibleFailure.end, Rule.FAILURE_STRING, Lint.Replacement.deleteFromTo(possibleFailure.pos, possibleFailure.end), - )); + ); } } } diff --git a/src/rules/noUnnecessaryCallbackWrapperRule.ts b/src/rules/noUnnecessaryCallbackWrapperRule.ts index f47447b4813..3e700ffc17b 100644 --- a/src/rules/noUnnecessaryCallbackWrapperRule.ts +++ b/src/rules/noUnnecessaryCallbackWrapperRule.ts @@ -49,9 +49,10 @@ function walk(ctx: Lint.WalkContext) { function cb(node: ts.Node): void { const fn = detectRedundantCallback(node); if (fn) { - const fix = ctx.createFix( + const fix = [ Lint.Replacement.deleteFromTo(node.getStart(), fn.getStart()), - Lint.Replacement.deleteFromTo(fn.getEnd(), node.getEnd())); + Lint.Replacement.deleteFromTo(fn.getEnd(), node.getEnd()), + ]; ctx.addFailureAtNode(node, Rule.FAILURE_STRING(fn.getText()), fix); } else { return ts.forEachChild(node, cb); diff --git a/src/rules/noUnnecessaryInitializerRule.ts b/src/rules/noUnnecessaryInitializerRule.ts index c3fda70d632..319307d4c2d 100644 --- a/src/rules/noUnnecessaryInitializerRule.ts +++ b/src/rules/noUnnecessaryInitializerRule.ts @@ -92,9 +92,9 @@ class Walker extends Lint.RuleWalker { } private failWithFix(node: ts.VariableDeclaration | ts.BindingElement | ts.ParameterDeclaration) { - const fix = this.createFix(this.deleteFromTo( + const fix = this.deleteFromTo( Lint.childOfKind(node, ts.SyntaxKind.EqualsToken)!.pos, - node.end)); + node.end); this.addFailureAtNode(node, Rule.FAILURE_STRING, fix); } } diff --git a/src/rules/noUnnecessaryQualifierRule.ts b/src/rules/noUnnecessaryQualifierRule.ts index 52646e1a766..b3c6c8dc5ee 100644 --- a/src/rules/noUnnecessaryQualifierRule.ts +++ b/src/rules/noUnnecessaryQualifierRule.ts @@ -80,7 +80,7 @@ class Walker extends Lint.ProgramAwareRuleWalker { private visitNamespaceAccess(node: ts.Node, qualifier: ts.EntityNameOrEntityNameExpression, name: ts.Identifier) { if (this.qualifierIsUnnecessary(qualifier, name)) { - const fix = this.createFix(this.deleteFromTo(qualifier.getStart(), name.getStart())); + const fix = this.deleteFromTo(qualifier.getStart(), name.getStart()); this.addFailureAtNode(qualifier, Rule.FAILURE_STRING(qualifier.getText()), fix); } else { // Only look for nested qualifier errors if we didn't already fail on the outer qualifier. diff --git a/src/rules/noUnusedVariableRule.ts b/src/rules/noUnusedVariableRule.ts index d897af528cf..dc42cc9de0c 100644 --- a/src/rules/noUnusedVariableRule.ts +++ b/src/rules/noUnusedVariableRule.ts @@ -179,7 +179,7 @@ function addImportSpecifierFailures(ctx: Lint.WalkContext, failures: Map, failures: Map, failures: Map, failures: Map string} = { class OrderedImportsWalker extends Lint.RuleWalker { private currentImportsBlock: ImportsBlock = new ImportsBlock(); // keep a reference to the last Fix object so when the entire block is replaced, the replacement can be added - private lastFix: Lint.Fix | null; + private lastFix: Lint.Replacement[] | null; private importSourcesOrderTransform: (x: string) => string; private namedImportsOrderTransform: (x: string) => string; @@ -177,7 +177,7 @@ class OrderedImportsWalker extends Lint.RuleWalker { this.currentImportsBlock.addImportDeclaration(this.getSourceFile(), node, source); if (previousSource && compare(source, previousSource) === -1) { - this.lastFix = this.createFix(); + this.lastFix = []; this.addFailureAtNode(node, Rule.IMPORT_SOURCES_UNORDERED, this.lastFix); } @@ -202,7 +202,7 @@ class OrderedImportsWalker extends Lint.RuleWalker { this.currentImportsBlock.replaceNamedImports(start, length, sortedDeclarations[i]); } - this.lastFix = this.createFix(); + this.lastFix = []; this.addFailureFromStartToEnd(a.getStart(), b.getEnd(), Rule.NAMED_IMPORTS_UNORDERED, this.lastFix); } @@ -224,7 +224,7 @@ class OrderedImportsWalker extends Lint.RuleWalker { if (this.lastFix != null) { const replacement = this.currentImportsBlock.getReplacement(); if (replacement != null) { - this.lastFix.replacements.push(replacement); + this.lastFix.push(replacement); } this.lastFix = null; } diff --git a/src/rules/preferConstRule.ts b/src/rules/preferConstRule.ts index 7b0b3bac843..ea93884a162 100644 --- a/src/rules/preferConstRule.ts +++ b/src/rules/preferConstRule.ts @@ -304,9 +304,7 @@ class PreferConstWalker extends Lint.AbstractWalker { !info.declarationInfo.reassignedSiblings && info.declarationInfo.isBlockScoped && !appliedFixes.has(info.declarationInfo.declarationList)) { - fix = this.createFix( - new Lint.Replacement(info.declarationInfo.declarationList!.getStart(this.sourceFile), 3, "const"), - ); + fix = new Lint.Replacement(info.declarationInfo.declarationList!.getStart(this.sourceFile), 3, "const"); // add only one fixer per VariableDeclarationList appliedFixes.add(info.declarationInfo.declarationList); } diff --git a/src/rules/preferMethodSignatureRule.ts b/src/rules/preferMethodSignatureRule.ts index 9ab1c93410c..e3ac05fbc17 100644 --- a/src/rules/preferMethodSignatureRule.ts +++ b/src/rules/preferMethodSignatureRule.ts @@ -51,9 +51,10 @@ class Walker extends Lint.RuleWalker { } private createMethodSignatureFix(node: ts.PropertyDeclaration, type: ts.FunctionTypeNode): Lint.Fix | undefined { - return type.type && this.createFix( + return type.type && [ this.deleteFromTo(Lint.childOfKind(node, ts.SyntaxKind.ColonToken)!.getStart(), type.getStart()), this.deleteFromTo(Lint.childOfKind(type, ts.SyntaxKind.EqualsGreaterThanToken)!.getStart(), type.type.getStart()), - this.appendText(Lint.childOfKind(type, ts.SyntaxKind.CloseParenToken)!.end, ":")); + this.appendText(Lint.childOfKind(type, ts.SyntaxKind.CloseParenToken)!.end, ":"), + ]; } } diff --git a/src/rules/quotemarkRule.ts b/src/rules/quotemarkRule.ts index 93a28d0ddd9..c0f1d267264 100644 --- a/src/rules/quotemarkRule.ts +++ b/src/rules/quotemarkRule.ts @@ -104,9 +104,9 @@ function walk(ctx: Lint.WalkContext) { } text = text.replace(new RegExp(`\\\\${actualQuoteMark}`, "g"), actualQuoteMark); - return ctx.addFailure(start, node.end, Rule.FAILURE_STRING(actualQuoteMark, expectedQuoteMark), ctx.createFix( + return ctx.addFailure(start, node.end, Rule.FAILURE_STRING(actualQuoteMark, expectedQuoteMark), new Lint.Replacement(start, node.end - start, expectedQuoteMark + text + expectedQuoteMark), - )); + ); } return ts.forEachChild(node, cb); }); diff --git a/src/rules/semicolonRule.ts b/src/rules/semicolonRule.ts index 3b2f6496358..df1c28fa2b4 100644 --- a/src/rules/semicolonRule.ts +++ b/src/rules/semicolonRule.ts @@ -189,9 +189,7 @@ class SemicolonWalker extends Lint.AbstractWalker { const hasSemicolon = lastChar === ";"; if (this.options.always && !hasSemicolon) { if (lastChar === ",") { - this.addFailureAt(member.end - 1, 1, Rule.FAILURE_STRING_COMMA, this.createFix( - new Lint.Replacement(member.end - 1, 1, ";"), - )); + this.addFailureAt(member.end - 1, 1, Rule.FAILURE_STRING_COMMA, new Lint.Replacement(member.end - 1, 1, ";")); } else { this.reportMissing(member.end); } @@ -203,15 +201,11 @@ class SemicolonWalker extends Lint.AbstractWalker { } private reportMissing(pos: number) { - this.addFailureAt(pos, 0, Rule.FAILURE_STRING_MISSING, this.createFix( - Lint.Replacement.appendText(pos, ";"), - )); + this.addFailureAt(pos, 0, Rule.FAILURE_STRING_MISSING, Lint.Replacement.appendText(pos, ";")); } private reportUnnecessary(pos: number, noFix?: boolean) { - this.addFailureAt(pos, 1, Rule.FAILURE_STRING_UNNECESSARY, noFix ? undefined : this.createFix( - Lint.Replacement.deleteText(pos, 1), - )); + this.addFailureAt(pos, 1, Rule.FAILURE_STRING_UNNECESSARY, noFix ? undefined : Lint.Replacement.deleteText(pos, 1)); } private checkSemicolonAt(node: ts.Node) { diff --git a/src/rules/spaceBeforeFunctionParenRule.ts b/src/rules/spaceBeforeFunctionParenRule.ts index be68799a4f5..d218e15db7a 100644 --- a/src/rules/spaceBeforeFunctionParenRule.ts +++ b/src/rules/spaceBeforeFunctionParenRule.ts @@ -159,12 +159,10 @@ class FunctionWalker extends Lint.RuleWalker { if (hasSpace && option === "never") { const pos = openParen.getStart() - 1; - const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.deleteText(pos, 1) ]); - this.addFailureAt(pos, 1, Rule.INVALID_WHITESPACE_ERROR, fix); + this.addFailureAt(pos, 1, Rule.INVALID_WHITESPACE_ERROR, this.deleteText(pos, 1)); } else if (!hasSpace && option === "always") { const pos = openParen.getStart(); - const fix = new Lint.Fix(Rule.metadata.ruleName, [ this.appendText(pos, " ") ]); - this.addFailureAt(pos, 1, Rule.MISSING_WHITESPACE_ERROR, fix); + this.addFailureAt(pos, 1, Rule.MISSING_WHITESPACE_ERROR, this.appendText(pos, " ")); } } diff --git a/src/rules/trailingCommaRule.ts b/src/rules/trailingCommaRule.ts index 950216a8302..64bb5dbddf9 100644 --- a/src/rules/trailingCommaRule.ts +++ b/src/rules/trailingCommaRule.ts @@ -165,13 +165,9 @@ class TrailingCommaWalker extends Lint.AbstractWalker { const closeTokenLine = ts.getLineAndCharacterOfPosition(this.sourceFile, closeTokenPos).line; const option = lastElementLine === closeTokenLine ? this.options.singleline : this.options.multiline; if (hasTrailingComma && option === "never") { - this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_NEVER, this.createFix( - Lint.Replacement.deleteText(list.end - 1, 1), - )); + this.addFailureAt(list.end - 1, 1, Rule.FAILURE_STRING_NEVER, Lint.Replacement.deleteText(list.end - 1, 1)); } else if (!hasTrailingComma && option === "always") { - this.addFailureAt(list.end, 0, Rule.FAILURE_STRING_ALWAYS, this.createFix( - Lint.Replacement.appendText(list.end, ","), - )); + this.addFailureAt(list.end, 0, Rule.FAILURE_STRING_ALWAYS, Lint.Replacement.appendText(list.end, ",")); } } } diff --git a/src/rules/whitespaceRule.ts b/src/rules/whitespaceRule.ts index 0df3e3ee239..58f882bb6f6 100644 --- a/src/rules/whitespaceRule.ts +++ b/src/rules/whitespaceRule.ts @@ -256,7 +256,7 @@ class WhitespaceWalker extends Lint.RuleWalker { } private addMissingWhitespaceErrorAt(position: number) { - const fix = this.createFix(this.appendText(position, " ")); + const fix = this.appendText(position, " "); this.addFailureAt(position, 1, Rule.FAILURE_STRING, fix); } } diff --git a/src/test.ts b/src/test.ts index 22bb95af0c2..d540d59dca9 100644 --- a/src/test.ts +++ b/src/test.ts @@ -23,10 +23,11 @@ import * as path from "path"; import * as semver from "semver"; import * as ts from "typescript"; -import {Fix} from "./language/rule/rule"; +import {Replacement} from "./language/rule/rule"; import * as Linter from "./linter"; import {LintError} from "./test/lintError"; import * as parse from "./test/parse"; +import {mapDefined} from "./utils"; const MARKUP_FILE_EXTENSION = ".lint"; const FIXES_FILE_EXTENSION = ".fix"; @@ -177,8 +178,8 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ const stat = fs.statSync(fixedFile); if (stat.isFile()) { fixedFileText = fs.readFileSync(fixedFile, "utf8"); - const fixes = failures.filter((f) => f.hasFix()).map((f) => f.getFix()) as Fix[]; - newFileText = Fix.applyAll(fileTextWithoutMarkup, fixes); + const fixes = mapDefined(failures, (f) => f.getFix()); + newFileText = Replacement.applyFixes(fileTextWithoutMarkup, fixes); } } catch (e) { fixedFileText = ""; diff --git a/src/utils.ts b/src/utils.ts index 16603a219e6..3bd336ed39a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -114,3 +114,24 @@ export type Equal = (a: T, b: T) => boolean; export function arraysAreEqual(a: T[] | undefined, b: T[] | undefined, eq: Equal): boolean { return a === b || !!a && !!b && a.length === b.length && a.every((x, idx) => eq(x, b[idx])); } + +/** Returns an array that is the concatenation of all output arrays. */ +export function flatMap(inputs: T[], getOutputs: (input: T) => U[]): U[] { + const out = []; + for (const input of inputs) { + out.push(...getOutputs(input)); + } + return out; +} + +/** Returns an array of all outputs that are not `undefined`. */ +export function mapDefined(inputs: T[], getOutput: (input: T) => U | undefined): U[] { + const out = []; + for (const input of inputs) { + const output = getOutput(input); + if (output !== undefined) { + out.push(output); + } + } + return out; +} diff --git a/test/formatters/jsonFormatterTests.ts b/test/formatters/jsonFormatterTests.ts index d485e22ec01..e923c8c8a6c 100644 --- a/test/formatters/jsonFormatterTests.ts +++ b/test/formatters/jsonFormatterTests.ts @@ -16,7 +16,7 @@ import * as ts from "typescript"; -import { Fix, IFormatter, Replacement, TestUtils } from "../lint"; +import { IFormatter, Replacement, TestUtils } from "../lint"; import { createFailure } from "./utils"; describe("JSON Formatter", () => { @@ -37,9 +37,7 @@ describe("JSON Formatter", () => { createFailure(sourceFile, 0, 1, "first failure", "first-name", undefined, "error"), createFailure(sourceFile, maxPosition - 1, maxPosition, "last failure", "last-name", undefined, "error"), createFailure(sourceFile, 0, maxPosition, "full failure", "full-name", - new Fix("full-name", [ - new Replacement(0, 0, ""), - ]), + new Replacement(0, 0, ""), "error"), ]; @@ -80,14 +78,9 @@ describe("JSON Formatter", () => { name: TEST_FILE, failure: "full failure", fix: { - innerReplacements: [ - { - innerLength: 0, - innerStart: 0, - innerText: "", - }, - ], - innerRuleName: "full-name", + innerLength: 0, + innerStart: 0, + innerText: "", }, startPosition: { position: 0,