diff --git a/src/rules/numberLiteralFormatRule.ts b/src/rules/numberLiteralFormatRule.ts index 5e48e394923..052a4c2a231 100644 --- a/src/rules/numberLiteralFormatRule.ts +++ b/src/rules/numberLiteralFormatRule.ts @@ -25,6 +25,7 @@ export class Rule extends Lint.Rules.AbstractRule { /* tslint:disable:object-literal-sort-keys */ public static metadata: Lint.IRuleMetadata = { ruleName: "number-literal-format", + hasFix: true, description: "Checks that decimal literals should begin with '0.' instead of just '.', and should not end with a trailing '0'.", optionsDescription: "Not configurable.", @@ -63,6 +64,7 @@ function walk(ctx: Lint.WalkContext): void { function check(node: ts.NumericLiteral): void { // Apparently the number literal '0.0' has a '.text' of '0', so use '.getText()' instead. const text = node.getText(sourceFile); + const start = node.getStart(); if (text.length <= 1) { return; @@ -72,8 +74,14 @@ function walk(ctx: Lint.WalkContext): void { // Hex/octal/binary number can't have decimal point or exponent, so no other errors possible. switch (text[1]) { case "x": - if (!isUpperCase(text.slice(2))) { - ctx.addFailureAtNode(node, Rule.FAILURE_STRING_NOT_UPPERCASE); + // strip "0x" + const hexNumber = text.slice(2); + if (!isUpperCase(hexNumber)) { + ctx.addFailureAtNode( + node, + Rule.FAILURE_STRING_NOT_UPPERCASE, + Lint.Replacement.replaceNode(node, `0x${hexNumber.toUpperCase()}`), + ); } return; case "o": @@ -82,14 +90,34 @@ function walk(ctx: Lint.WalkContext): void { case ".": break; default: - ctx.addFailureAtNode(node, Rule.FAILURE_STRING_LEADING_0); + ctx.addFailureAtNode( + node, + Rule.FAILURE_STRING_LEADING_0, + Lint.Replacement.deleteFromTo(start, start + /^0+/.exec(text)![0].length), + ); return; } } - const [num, exp] = text.split(/e/i); - if (exp !== undefined && (exp.startsWith("-0") || exp.startsWith("0"))) { - ctx.addFailureAt(node.getEnd() - exp.length, exp.length, Rule.FAILURE_STRING_LEADING_0); + const [num, exp = ""] = text.split(/e/i); + const [integer, float = ""] = num.split("."); + const matchedNumeric = /(\.)([1-9]*)(0+)/.exec(num); + const [dot = "", numbers = "", zeroes = ""] = Array.isArray(matchedNumeric) + ? matchedNumeric.slice(1) + : []; + + if (exp.startsWith("-0") || exp.startsWith("0")) { + const expStart = start + num.length + 1; // position of exp part + const expNumberStart = /\D/.test(exp.charAt(0)) ? expStart + 1 : expStart; // do not remove "-" or "+" + ctx.addFailureAt( + node.getEnd() - exp.length, + exp.length, + Rule.FAILURE_STRING_LEADING_0, + Lint.Replacement.deleteFromTo( + expNumberStart, + expNumberStart + /0+/.exec(exp)![0].length, + ), + ); } if (!num.includes(".")) { @@ -97,20 +125,29 @@ function walk(ctx: Lint.WalkContext): void { } if (num.startsWith(".")) { - fail(Rule.FAILURE_STRING_LEADING_DECIMAL); - } - - if (num.endsWith(".")) { - fail(Rule.FAILURE_STRING_TRAILING_DECIMAL); + // .1 -> 0.1 + fail(Rule.FAILURE_STRING_LEADING_DECIMAL, Lint.Replacement.appendText(start, "0")); + } else if (num.endsWith(".")) { + // 1. -> 1 + fail( + Rule.FAILURE_STRING_TRAILING_DECIMAL, + Lint.Replacement.deleteText(start + num.length - 1, 1), + ); } // Allow '10', but not '1.0' - if (num.endsWith("0")) { - fail(Rule.FAILURE_STRING_TRAILING_0); + if (float.endsWith("0")) { + // 1.0 -> 1 + const offset = numbers.length > 0 ? dot.length + numbers.length : 0; + const length = (numbers.length > 0 ? 0 : dot.length) + zeroes.length; + fail( + Rule.FAILURE_STRING_TRAILING_0, + Lint.Replacement.deleteText(start + integer.length + offset, length), + ); } - function fail(message: string): void { - ctx.addFailureAt(node.getStart(sourceFile), num.length, message); + function fail(message: string, fix?: Lint.Replacement | Lint.Replacement[]): void { + ctx.addFailureAt(node.getStart(sourceFile), num.length, message, fix); } } } diff --git a/test/rules/number-literal-format/test.ts.fix b/test/rules/number-literal-format/test.ts.fix new file mode 100644 index 00000000000..5eb2601034c --- /dev/null +++ b/test/rules/number-literal-format/test.ts.fix @@ -0,0 +1,45 @@ +0; +0.5; +10; +1.1e10; + +-6000; + +-777; + +-0; + +-0.9; + +-0.2; + +-0.5; + +-123e3; + +-145E4; + +-1467e-8; + +-189e10; + +-0xDEADBEEF; + +1; + +1 + +0; +0.5; + +0.5; + +0.5; + +1e1; +1E1; +1e-1; +1e10; + +0xDEADBEEF; + diff --git a/test/rules/number-literal-format/test.ts.lint b/test/rules/number-literal-format/test.ts.lint index 0b6b363e78d..36b285dd244 100644 --- a/test/rules/number-literal-format/test.ts.lint +++ b/test/rules/number-literal-format/test.ts.lint @@ -3,6 +3,40 @@ 10; 1.1e10; +-0000006000; + ~~~~~~~~~~ [leading-0] + +-777.; + ~~~~ [trailing-decimal] + +-0.000; + ~~~~~ [trailing-0] + +-0.90000; + ~~~~~~~ [trailing-0] + +-.2; + ~~ [leading-decimal] + +-.50000; + ~~~~~~ [trailing-0] + ~~~~~~ [leading-decimal] + +-123e0003; + ~~~~ [leading-0] + +-145E0004; + ~~~~ [leading-0] + +-1467e-0008; + ~~~~~ [leading-0] + +-189.000e10; + ~~~~~~~ [trailing-0] + +-0xDEAdBEEF; + ~~~~~~~~~~ [uppercase] + 01; ~~ [leading-0]