From 15c057320bd570907d1136c852405bc5c34f9507 Mon Sep 17 00:00:00 2001 From: Armano Date: Mon, 17 Dec 2018 22:46:58 +0100 Subject: [PATCH 1/3] Add rule `no-semantic-errors` --- lib/rules/no-semantic-errors.js | 57 +++++++++++++++ package.json | 2 +- tests/lib/fixtures/empty/empty.ts | 0 tests/lib/fixtures/empty/import.ts | 3 + tests/lib/fixtures/empty/tsconfig.json | 12 ++++ tests/lib/rules/no-semantic-errors.js | 99 ++++++++++++++++++++++++++ yarn.lock | 17 +++-- 7 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 lib/rules/no-semantic-errors.js create mode 100644 tests/lib/fixtures/empty/empty.ts create mode 100644 tests/lib/fixtures/empty/import.ts create mode 100644 tests/lib/fixtures/empty/tsconfig.json create mode 100644 tests/lib/rules/no-semantic-errors.js diff --git a/lib/rules/no-semantic-errors.js b/lib/rules/no-semantic-errors.js new file mode 100644 index 0000000..004c814 --- /dev/null +++ b/lib/rules/no-semantic-errors.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Enforces that there is no semantic and syntactic errors + * @author Armano + */ +"use strict"; + +const util = require("../util"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: + "Enforces that there is no semantic and syntactic errors", + category: "TypeScript", + url: util.metaDocsUrl("no-semantic-errors"), + }, + schema: [], + }, + + create(context) { + const sourceCode = context.getSourceCode(); + + const program = + context.parserServices && context.parserServices.program; + + if (!program) { + return []; + } + + return { + Program(node) { + const semantic = program.getSemanticDiagnostics() || []; + const syntactic = program.getSyntacticDiagnostics() || []; + + const errors = semantic + .concat(syntactic) + // DiagnosticCategory.Error = 1, + .filter(error => error.category === 1); + + for (const error of errors) { + const errorNode = error.start + ? sourceCode.getNodeByRangeIndex(error.start) + : node; + + context.report({ + node: errorNode, + message: error.messageText, + }); + } + }, + }; + }, +}; diff --git a/package.json b/package.json index f13eafb..eefdcd8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "requireindex": "^1.2.0", - "typescript-eslint-parser": "21.0.2" + "typescript-eslint-parser": "git+https://github.com/uniqueiniquity/typescript-eslint-parser.git#0625f29a9721c6f10a7522de6c9d236a671bd1ac" }, "devDependencies": { "eslint": "^5.9.0", diff --git a/tests/lib/fixtures/empty/empty.ts b/tests/lib/fixtures/empty/empty.ts new file mode 100644 index 0000000..e69de29 diff --git a/tests/lib/fixtures/empty/import.ts b/tests/lib/fixtures/empty/import.ts new file mode 100644 index 0000000..115a937 --- /dev/null +++ b/tests/lib/fixtures/empty/import.ts @@ -0,0 +1,3 @@ +export default interface Foo { + name: string +} diff --git a/tests/lib/fixtures/empty/tsconfig.json b/tests/lib/fixtures/empty/tsconfig.json new file mode 100644 index 0000000..412bf8e --- /dev/null +++ b/tests/lib/fixtures/empty/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "lib": [ + "es2015", + "es2017" + ] + } +} diff --git a/tests/lib/rules/no-semantic-errors.js b/tests/lib/rules/no-semantic-errors.js new file mode 100644 index 0000000..2792e48 --- /dev/null +++ b/tests/lib/rules/no-semantic-errors.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Enforces that there is no semantic and syntactic errors + * @author Armano + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const path = require("path"); + +const rule = require("../../../lib/rules/no-semantic-errors"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const rootPath = path.join(process.cwd(), "tests/lib/fixtures/empty"); +// valid filePath is required to get access to lib +// @see https://github.com/JamesHenry/typescript-estree/issues/50 +const filePath = path.join(rootPath, "empty.ts"); + +const ruleTester = new RuleTester({ + parser: "typescript-eslint-parser", + parserOptions: { + generateServices: true, + tsconfigRootDir: rootPath, + project: "./tsconfig.json", + }, +}); + +ruleTester.run("no-errors", rule, { + valid: [ + { + filename: filePath, + code: ` +import Foo from './import'; +var foo: Foo = { + name: 'test' +}; + `, + }, + { + filename: filePath, + code: `var foo: number = parseInt("5.5", 10) + 10;`, + }, + ], + invalid: [ + { + filename: filePath, + code: `var foo: string = parseInt("5.5", 10) + 10;`, + errors: [ + { + message: + "Type 'number' is not assignable to type 'string'.", + line: 1, + column: 5, + type: "Identifier", + }, + ], + }, + { + filename: filePath, + code: ` +import Foo from './import'; +var foo: Foo = { + name: 2 +}; + `, + errors: [ + { + message: + "Type 'number' is not assignable to type 'string'.", + line: 4, + column: 5, + type: "Identifier", + }, + ], + }, + { + filename: filePath, + code: ` +import Foo from './not-found'; +var foo: Foo = { + name: 2 +}; + `, + errors: [ + { + message: "Cannot find module './not-found'.", + line: 2, + column: 17, + type: "Literal", + }, + ], + }, + ], +}); diff --git a/yarn.lock b/yarn.lock index 91ea910..6263206 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2997,15 +2997,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript-eslint-parser@21.0.2: - version "21.0.2" - resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-21.0.2.tgz#270af10e4724528677fbcf34ea495284bec3a894" - integrity sha512-u+pj4RVJBr4eTzj0n5npoXD/oRthvfUCjSKndhNI714MG0mQq2DJw5WP7qmonRNIFgmZuvdDOH3BHm9iOjIAfg== - dependencies: - eslint-scope "^4.0.0" - eslint-visitor-keys "^1.0.0" - typescript-estree "5.3.0" - typescript-eslint-parser@^16.0.0: version "16.0.1" resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-16.0.1.tgz#b40681c7043b222b9772748b700a000b241c031b" @@ -3014,6 +3005,14 @@ typescript-eslint-parser@^16.0.0: lodash.unescape "4.0.1" semver "5.5.0" +"typescript-eslint-parser@git+https://github.com/uniqueiniquity/typescript-eslint-parser.git#0625f29a9721c6f10a7522de6c9d236a671bd1ac": + version "21.0.2" + resolved "git+https://github.com/uniqueiniquity/typescript-eslint-parser.git#0625f29a9721c6f10a7522de6c9d236a671bd1ac" + dependencies: + eslint-scope "^4.0.0" + eslint-visitor-keys "^1.0.0" + typescript-estree "5.3.0" + typescript-estree@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/typescript-estree/-/typescript-estree-5.3.0.tgz#fb6c977b5e21073eb16cbdc0338a7f752da99ff5" From 4e8b91b84e7175a17e0f018b4f31b6df0d54bf83 Mon Sep 17 00:00:00 2001 From: Armano Date: Mon, 17 Dec 2018 23:58:32 +0100 Subject: [PATCH 2/3] correct handling messageText for nested errors and add documentation --- README.md | 1 + docs/rules/no-semantic-errors.md | 31 +++++++++++++++++++++++++ lib/rules/no-semantic-errors.js | 5 +++- tests/lib/rules/no-semantic-errors.js | 33 +++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 docs/rules/no-semantic-errors.md diff --git a/README.md b/README.md index af9ea5e..4460bb3 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ This guarantees 100% compatibility between the plugin and the parser. | [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | | | | [`typescript/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | | | | [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | | | +| [`typescript/no-semantic-errors`](./docs/rules/no-semantic-errors.md) | Enforces that there is no semantic and syntactic errors | | | | [`typescript/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | | [`typescript/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | | | | [`typescript/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | diff --git a/docs/rules/no-semantic-errors.md b/docs/rules/no-semantic-errors.md new file mode 100644 index 0000000..6136980 --- /dev/null +++ b/docs/rules/no-semantic-errors.md @@ -0,0 +1,31 @@ +# Enforces that there is no semantic and syntactic errors (no-semantic-errors) + +This rule reports all semantic and type errors provided by diagnostics from typescript. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +interface Foo { + hello: string; +} +const foo: string = ({ hello: 2 } as Foo)!.foo +``` + +Examples of **correct** code for this rule: + +```ts +interface Foo { + hello: string; +} +const foo: string = ({ hello: 'Bar' } as Foo).hello +``` + +### Options + +```json +{ + "typescript/no-this-alias": "no-semantic-errors" +} +``` diff --git a/lib/rules/no-semantic-errors.js b/lib/rules/no-semantic-errors.js index 004c814..52d3930 100644 --- a/lib/rules/no-semantic-errors.js +++ b/lib/rules/no-semantic-errors.js @@ -48,7 +48,10 @@ module.exports = { context.report({ node: errorNode, - message: error.messageText, + message: + typeof error.messageText === "object" + ? error.messageText.messageText + : error.messageText, }); } }, diff --git a/tests/lib/rules/no-semantic-errors.js b/tests/lib/rules/no-semantic-errors.js index 2792e48..e49942d 100644 --- a/tests/lib/rules/no-semantic-errors.js +++ b/tests/lib/rules/no-semantic-errors.js @@ -41,6 +41,15 @@ var foo: Foo = { }; `, }, + { + filename: filePath, + code: ` +interface Foo { + hello: string; +} +const foo: string = ({ hello: 'Bar' } as Foo).hello + `, + }, { filename: filePath, code: `var foo: number = parseInt("5.5", 10) + 10;`, @@ -95,5 +104,29 @@ var foo: Foo = { }, ], }, + { + filename: filePath, + code: ` +interface Foo { + hello: string; +} +const foo: string = ({ hello: 2 } as Foo)!.foo + `, + errors: [ + { + message: + "Conversion of type '{ hello: number; }' to type 'Foo' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.", + line: 5, + column: 22, + type: "ObjectExpression", + }, + { + message: "Property 'foo' does not exist on type 'Foo'.", + line: 5, + column: 44, + type: "Identifier", + }, + ], + }, ], }); From b0cd079df1ce7e3fd9b7561a179989a610cde319 Mon Sep 17 00:00:00 2001 From: Armano Date: Tue, 18 Dec 2018 21:05:35 +0100 Subject: [PATCH 3/3] Add util method to retrieve parserServices from context --- docs/rules/no-semantic-errors.md | 4 ++-- lib/rules/no-semantic-errors.js | 8 ++------ lib/util.js | 18 ++++++++++++++++++ tests/lib/fixtures/empty/tsconfig.json | 5 +---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/rules/no-semantic-errors.md b/docs/rules/no-semantic-errors.md index 6136980..b531683 100644 --- a/docs/rules/no-semantic-errors.md +++ b/docs/rules/no-semantic-errors.md @@ -10,7 +10,7 @@ Examples of **incorrect** code for this rule: interface Foo { hello: string; } -const foo: string = ({ hello: 2 } as Foo)!.foo +const foo: string = ({ hello: 2 } as Foo)!.foo; ``` Examples of **correct** code for this rule: @@ -19,7 +19,7 @@ Examples of **correct** code for this rule: interface Foo { hello: string; } -const foo: string = ({ hello: 'Bar' } as Foo).hello +const foo: string = ({ hello: "Bar" } as Foo).hello; ``` ### Options diff --git a/lib/rules/no-semantic-errors.js b/lib/rules/no-semantic-errors.js index 52d3930..d77f22a 100644 --- a/lib/rules/no-semantic-errors.js +++ b/lib/rules/no-semantic-errors.js @@ -12,6 +12,7 @@ const util = require("../util"); module.exports = { meta: { + type: "problem", docs: { description: "Enforces that there is no semantic and syntactic errors", @@ -24,12 +25,7 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); - const program = - context.parserServices && context.parserServices.program; - - if (!program) { - return []; - } + const program = util.getParserServices(context).program; return { Program(node) { diff --git a/lib/util.js b/lib/util.js index a44369a..d895f4f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -15,6 +15,24 @@ exports.metaDocsUrl = name => */ exports.isTypescript = fileName => /\.tsx?$/.test(fileName); +/** + * Try to retrieve typescript parser service from context + * @param {RuleContext} context Rule context + * @returns {{esTreeNodeToTSNodeMap}|{program}|Object|*} parserServices + */ +exports.getParserServices = context => { + if ( + !context.parserServices || + !context.parserServices.program || + !context.parserServices.esTreeNodeToTSNodeMap + ) { + throw new Error( + "This rule requires you to use `typescript-eslint-parser`." + ); + } + return context.parserServices; +}; + /** * Pure function - doesn't mutate either parameter! * Merges two objects together deeply, overwriting the properties in first with the properties in second diff --git a/tests/lib/fixtures/empty/tsconfig.json b/tests/lib/fixtures/empty/tsconfig.json index 412bf8e..2dd72f8 100644 --- a/tests/lib/fixtures/empty/tsconfig.json +++ b/tests/lib/fixtures/empty/tsconfig.json @@ -4,9 +4,6 @@ "module": "commonjs", "strict": true, "esModuleInterop": true, - "lib": [ - "es2015", - "es2017" - ] + "lib": ["es2015", "es2017"] } }