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..b531683 --- /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 new file mode 100644 index 0000000..d77f22a --- /dev/null +++ b/lib/rules/no-semantic-errors.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Enforces that there is no semantic and syntactic errors + * @author Armano + */ +"use strict"; + +const util = require("../util"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + 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 = util.getParserServices(context).program; + + 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: + typeof error.messageText === "object" + ? error.messageText.messageText + : error.messageText, + }); + } + }, + }; + }, +}; 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/package.json b/package.json index 0370308..2e4673f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,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..2dd72f8 --- /dev/null +++ b/tests/lib/fixtures/empty/tsconfig.json @@ -0,0 +1,9 @@ +{ + "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..e49942d --- /dev/null +++ b/tests/lib/rules/no-semantic-errors.js @@ -0,0 +1,132 @@ +/** + * @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: ` +interface Foo { + hello: string; +} +const foo: string = ({ hello: 'Bar' } as Foo).hello + `, + }, + { + 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", + }, + ], + }, + { + 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", + }, + ], + }, + ], +}); diff --git a/yarn.lock b/yarn.lock index c9dd2af..94955ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2996,15 +2996,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" @@ -3013,6 +3004,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"