From afafd3fcb3357c831d15d526b069aa9619083124 Mon Sep 17 00:00:00 2001 From: Alexander T Date: Fri, 2 Aug 2019 18:27:06 +0300 Subject: [PATCH] simple-indent --- scripts/eslint/rules/simple-indent.ts | 72 ++++ scripts/eslint/tests/boolean-trivia.test.ts | 13 +- scripts/eslint/tests/fixtures/file.ts | 0 .../tests/{support => fixtures}/tsconfig.json | 18 +- scripts/eslint/tests/no-double-space.test.ts | 30 +- scripts/eslint/tests/simple-indent.test.ts | 333 ++++++++++++++++++ scripts/eslint/tests/support/RuleTester.ts | 3 +- scripts/eslint/tsconfig.json | 6 +- src/compiler/types.ts | 3 +- 9 files changed, 462 insertions(+), 16 deletions(-) create mode 100644 scripts/eslint/rules/simple-indent.ts create mode 100644 scripts/eslint/tests/fixtures/file.ts rename scripts/eslint/tests/{support => fixtures}/tsconfig.json (69%) create mode 100644 scripts/eslint/tests/simple-indent.test.ts diff --git a/scripts/eslint/rules/simple-indent.ts b/scripts/eslint/rules/simple-indent.ts new file mode 100644 index 00000000000..d9ab40d5252 --- /dev/null +++ b/scripts/eslint/rules/simple-indent.ts @@ -0,0 +1,72 @@ +import { TSESTree } from "@typescript-eslint/typescript-estree"; +import { createRule } from "./utils"; + +type MessageId = "simpleIndentError"; +type Options = any; + +export = createRule({ + name: "simple-indent", + meta: { + docs: { + description: "Enforce consistent indentation", + category: "Stylistic Issues", + recommended: "error", + }, + messages: { + simpleIndentError: "4 space indentation expected", + }, + fixable: 'whitespace', + schema: [], + type: "layout", + }, + defaultOptions: [], + create(context) { + const TAB_SIZE = 4; + const TAB_REGEX = /\t/g; + const sourceCode = context.getSourceCode(); + const linebreaks = sourceCode.getText().match(/\r\n|[\r\n\u2028\u2029]/gu); + + const checkIndent = (node: TSESTree.Program) => { + const lines = sourceCode.getLines(); + const linesLen = lines.length; + let totalLen = 0; + + for (let i = 0; i < linesLen; i++) { + const lineNumber = i + 1; + const line = lines[i]; + const linebreaksLen = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; + const lineLen = line.length + linebreaksLen; + const matches = /\S/.exec(line); + + if (matches && matches.index) { + const indentEnd = matches.index; + const whitespace = line.slice(0, indentEnd); + + if (!TAB_REGEX.test(whitespace)) { + totalLen += lineLen; + continue; + } + + context.report({ + messageId: 'simpleIndentError', + node, + loc: { column: indentEnd, line: lineNumber }, + fix(fixer) { + const rangeStart = totalLen; + const rangeEnd = rangeStart + indentEnd; + + return fixer + .replaceTextRange([rangeStart, rangeEnd], whitespace.replace(TAB_REGEX, " ".repeat(TAB_SIZE))); + } + }); + } + + totalLen += lineLen; + } + } + + return { + Program: checkIndent, + } + }, +}); diff --git a/scripts/eslint/tests/boolean-trivia.test.ts b/scripts/eslint/tests/boolean-trivia.test.ts index 0758bdbb12b..79f35d94cb6 100644 --- a/scripts/eslint/tests/boolean-trivia.test.ts +++ b/scripts/eslint/tests/boolean-trivia.test.ts @@ -1,8 +1,6 @@ -import { RuleTester, ROOT_DIR } from "./support/RuleTester"; +import { RuleTester, ROOT_DIR, FILENAME } from "./support/RuleTester"; import rule = require("../rules/boolean-trivia"); - - const ruleTester = new RuleTester({ parserOptions: { warnOnUnsupportedTypeScriptVersion: false, @@ -17,21 +15,25 @@ const ruleTester = new RuleTester({ ruleTester.run("boolean-trivia", rule, { valid: [{ + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn(/* boolean prop */ true); `, }, { + filename: FILENAME, code: ` const fn = (prop: null) => {}; fn(/* null prop */ null); `, }, { + filename: FILENAME, code: ` const fn = (prop: null) => {}; fn(/*null prop*/ null); `, }, { + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn(/* comment */ @@ -39,6 +41,7 @@ fn(/* comment */ ); `, }, { + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn.apply(null, true); @@ -46,24 +49,28 @@ fn.apply(null, true); }], invalid: [{ + filename: FILENAME, code: ` const fn = (prop: null) => {}; fn(null); `, errors: [{ messageId: "booleanTriviaArgumentError" }], }, { + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn(false); `, errors: [{ messageId: "booleanTriviaArgumentError" }], }, { + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn(/* boolean arg */false); `, errors: [{ messageId: "booleanTriviaArgumentSpaceError" }], }, { + filename: FILENAME, code: ` const fn = (prop: boolean) => {}; fn(/* first comment */ /* second comment */ false); diff --git a/scripts/eslint/tests/fixtures/file.ts b/scripts/eslint/tests/fixtures/file.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/eslint/tests/support/tsconfig.json b/scripts/eslint/tests/fixtures/tsconfig.json similarity index 69% rename from scripts/eslint/tests/support/tsconfig.json rename to scripts/eslint/tests/fixtures/tsconfig.json index 9a059bababf..83c06141f7a 100644 --- a/scripts/eslint/tests/support/tsconfig.json +++ b/scripts/eslint/tests/fixtures/tsconfig.json @@ -1,9 +1,9 @@ -{ - "compilerOptions": { - "esModuleInterop": true, - "target": "es5", - "module": "commonjs", - "strict": true, - "lib": ["es2015", "es2017", "esnext"] - } -} \ No newline at end of file +{ + "compilerOptions": { + "esModuleInterop": true, + "target": "es5", + "module": "commonjs", + "strict": true, + "lib": ["es2015", "es2017", "esnext"], + } +} diff --git a/scripts/eslint/tests/no-double-space.test.ts b/scripts/eslint/tests/no-double-space.test.ts index de9b25a80dc..6d5ea4aa080 100644 --- a/scripts/eslint/tests/no-double-space.test.ts +++ b/scripts/eslint/tests/no-double-space.test.ts @@ -1,4 +1,4 @@ -import { RuleTester, ROOT_DIR } from "./support/RuleTester"; +import { RuleTester, ROOT_DIR, FILENAME } from "./support/RuleTester"; import rule = require("../rules/no-double-space"); const ruleTester = new RuleTester({ @@ -15,51 +15,69 @@ const ruleTester = new RuleTester({ ruleTester.run("no-double-space", rule, { valid: [{ + filename: FILENAME, code: `const a = {};`, }, { + filename: FILENAME, code: `function fn() {}`, }, { + filename: FILENAME, code: `const a = " ";`, }, { + filename: FILENAME, code: `// ^ ^`, }, { + filename: FILENAME, code: `class Cl {}`, }, { + filename: FILENAME, code: `// comment `, }, { + filename: FILENAME, code: `/* comment */`, }, { + filename: FILENAME, code: `" string ";`, }, { + filename: FILENAME, code: `/ regexp /g;`, }, { + filename: FILENAME, code: `const rgx = / regexp /g;`, }, { + filename: FILENAME, code: "const str = ` string template`;", }, { + filename: FILENAME, code: ` // comment`, }, { + filename: FILENAME, code: ` /* comment */`, }, { + filename: FILENAME, code: `// `, }, { + filename: FILENAME, code: ` const a = 1; `, }, { + filename: FILENAME, code: ` /** * comment */ `, }, { + filename: FILENAME, code: ` // comment // - comment // - comment `, }, { + filename: FILENAME, code: ` interface Props { prop: string[]; // comment prop @@ -67,6 +85,7 @@ interface Props { } `, }, { + filename: FILENAME, code: ` /** * Returns a JSON-encoded value of the type: string[] @@ -76,6 +95,7 @@ interface Props { */ `, }, { + filename: FILENAME, code: ` const obj = { content: "function f() { 1; }", @@ -84,27 +104,35 @@ const obj = { }], invalid: [{ + filename: FILENAME, code: `const a = {};`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 6 }], }, { + filename: FILENAME, code: `function fn() {}`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 9 }], }, { + filename: FILENAME, code: `class Cl {}`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 6 }], }, { + filename: FILENAME, code: "const str = ` string template`;", errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }], }, { + filename: FILENAME, code: `/** comment */`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }], }, { + filename: FILENAME, code: `/** comment with many spaces */`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }], }, { + filename: FILENAME, code: `// comment with many spaces`, errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 11 }], }, { + filename: FILENAME, code: ` const a = 1; const b = 2; diff --git a/scripts/eslint/tests/simple-indent.test.ts b/scripts/eslint/tests/simple-indent.test.ts new file mode 100644 index 00000000000..af655a4cfcf --- /dev/null +++ b/scripts/eslint/tests/simple-indent.test.ts @@ -0,0 +1,333 @@ +import { RuleTester } from "./support/RuleTester"; +import rule = require("../rules/simple-indent"); + +const ruleTester = new RuleTester({ + parserOptions: { + warnOnUnsupportedTypeScriptVersion: false, + }, + parser: require.resolve("@typescript-eslint/parser"), +}); + +ruleTester.run("simple-indent", rule, { + valid: [ + { + code: ` +/** + * Comment + */ + ` + }, + { + code: ` +module TestModule { + var func = () => { + console.warn("hi"); + }; +} + `, + }, + { + code: ` +class TestClass { + private variable; + + testFunction() { + this.variable = 3; + } +} + `, + }, + { + code: ` +var obj = { + a: 1, + b: 2, + c: 3 +}; + `, + }, + { + code: ` +export enum TestEnum { + VALUE1, + VALUE2 +} + `, + }, + { + code: ` +switch (integerValue) { + case 1: + console.warn("1"); + break; + default: + console.warn("default"); + break; +} + `, + }, + { + code: ` +function loops() { + for (var i = 0; i < 1; ++i) { + console.warn(i); + } + + while (i < 1) { + console.warn(i); + } + + do { + console.warn(i); + } while (i < 1); + + if (i < 1) { + console.warn(i); + } else { + console.warn(i + 1); + } +} + `, + }, + ], + + invalid: [ + { + code: ` +module TestModule { +\tvar testVariable = 123; +} + `, + output: ` +module TestModule { + var testVariable = 123; +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ], + }, + { + code: ` +function a() { +\t\tvar test = 123; +} + `, + output: ` +function a() { + var test = 123; +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 3 }, + ], + }, + { + code: ` +class TestClass { +\tprivate variable; + +\ttestFunction() { +\t\tthis.variable = 3; +\t} +} + `, + output: ` +class TestClass { + private variable; + + testFunction() { + this.variable = 3; + } +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + { messageId: 'simpleIndentError', line: 5, column: 2 }, + { messageId: 'simpleIndentError', line: 6, column: 3 }, + { messageId: 'simpleIndentError', line: 7, column: 2 }, + ], + }, + { + code: ` +var obj = { +\ta: 1, +\tb: 2, +\tc: 3 +}; + `, + output: ` +var obj = { + a: 1, + b: 2, + c: 3 +}; + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + { messageId: 'simpleIndentError', line: 4, column: 2 }, + { messageId: 'simpleIndentError', line: 5, column: 2 }, + ] + }, + { + code: ` +enum TestEnum { +\tVALUE1, + VALUE2 +} + `, + output: ` +enum TestEnum { + VALUE1, + VALUE2 +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ], + }, + { + code: ` +switch (integerValue) { +\tcase 0: +\t\tconsole.warn("1"); +\t\tbreak; + case 1: + console.warn("1"); + break; +\tdefault: +\t\tconsole.log("2"); +\t\tbreak; +} + `, + output: ` +switch (integerValue) { + case 0: + console.warn("1"); + break; + case 1: + console.warn("1"); + break; + default: + console.log("2"); + break; +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + { messageId: 'simpleIndentError', line: 4, column: 3 }, + { messageId: 'simpleIndentError', line: 5, column: 3 }, + { messageId: 'simpleIndentError', line: 9, column: 2 }, + { messageId: 'simpleIndentError', line: 10, column: 3 }, + { messageId: 'simpleIndentError', line: 11, column: 3 }, + ] + }, + { + code: ` +for (var i = 0; i < 1; ++i) { +\tconsole.warn("123"); +} + `, + output: ` +for (var i = 0; i < 1; ++i) { + console.warn("123"); +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ] + }, + { + code: ` +while (i < 1) { +\tconsole.warn("123"); +} + `, + output: ` +while (i < 1) { + console.warn("123"); +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ] + }, + { + code: ` +do { +\tconsole.warn("123"); +} while (i < 1); + `, + output: ` +do { + console.warn("123"); +} while (i < 1); + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ] + }, + { + code: ` +if (i < 1) { +\tconsole.warn("123"); +} + `, + output: ` +if (i < 1) { + console.warn("123"); +} + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ] + }, + { + code: ` +var arr = [ +\t1, + 2 +]; + `, + output: ` +var arr = [ + 1, + 2 +]; + `, + errors: [ + { messageId: 'simpleIndentError', line: 3, column: 2 }, + ] + }, + { + code: ` +var arr2 = [ + { +\t\ta: 1, + b: 2 + }, + { + a: 3, +\t\tb: 4 + } +]; + `, + output: ` +var arr2 = [ + { + a: 1, + b: 2 + }, + { + a: 3, + b: 4 + } +]; + `, + errors: [ + { messageId: 'simpleIndentError', line: 4, column: 3 }, + { messageId: 'simpleIndentError', line: 9, column: 3 }, + ] + } + ], +}); diff --git a/scripts/eslint/tests/support/RuleTester.ts b/scripts/eslint/tests/support/RuleTester.ts index 56a06427fc7..6c87329981f 100644 --- a/scripts/eslint/tests/support/RuleTester.ts +++ b/scripts/eslint/tests/support/RuleTester.ts @@ -1,5 +1,6 @@ import * as path from "path"; import { TSESLint } from "@typescript-eslint/experimental-utils"; -export const ROOT_DIR = path.resolve(__dirname); +export const ROOT_DIR = path.join(process.cwd(), 'scripts', 'eslint', 'tests', 'fixtures'); +export const FILENAME = path.join(ROOT_DIR, 'file.ts'); export const RuleTester = TSESLint.RuleTester; diff --git a/scripts/eslint/tsconfig.json b/scripts/eslint/tsconfig.json index edeac25c7f3..8832e89713a 100644 --- a/scripts/eslint/tsconfig.json +++ b/scripts/eslint/tsconfig.json @@ -15,7 +15,7 @@ "noResolve": false, "strict": true, "module": "commonjs", - "target": "es5", + "target": "es6", "outDir": "../../built/eslint", "lib": ["es2015", "es2016"] }, @@ -24,5 +24,9 @@ "rules", "tests", "tests/support/*.json" + ], + + "exclude": [ + "tests/fixtures" ] } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 65e847df680..78c8f5a3e13 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5916,7 +5916,8 @@ namespace ts { // If fileName is provided, gets all the diagnostics associated with that file name. // Otherwise, returns all the diagnostics (global and file associated) in this collection. - getDiagnostics(fileName?: string): DiagnosticWithLocation[]; + getDiagnostics(): Diagnostic[]; + getDiagnostics(fileName: string): DiagnosticWithLocation[]; reattachFileDiagnostics(newFile: SourceFile): void; }