diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index 00d11b528344..2d10621b7a66 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -153,6 +153,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: |
| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: |
+| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | |
diff --git a/packages/eslint-plugin/docs/semi.md b/packages/eslint-plugin/docs/semi.md
new file mode 100644
index 000000000000..69fd5c57be84
--- /dev/null
+++ b/packages/eslint-plugin/docs/semi.md
@@ -0,0 +1,25 @@
+# require or disallow semicolons instead of ASI (semi)
+
+This rule enforces consistent use of semicolons.
+
+## Rule Details
+
+This rule extends the base [eslint/semi](https://eslint.org/docs/rules/semi) rule.
+It supports all options and features of the base rule.
+This version adds support for numerous typescript features.
+
+## How to use
+
+```cjson
+{
+ // note you must disable the base rule as it can report incorrect errors
+ "semi": "off",
+ "@typescript-eslint/semi": ["error"]
+}
+```
+
+## Options
+
+See [eslint/semi options](https://eslint.org/docs/rules/semi#options).
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/semi.md)
diff --git a/packages/eslint-plugin/src/rules/semi.ts b/packages/eslint-plugin/src/rules/semi.ts
new file mode 100644
index 000000000000..89fe6180333f
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/semi.ts
@@ -0,0 +1,66 @@
+import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree';
+import baseRule from 'eslint/lib/rules/semi';
+import { RuleListener, RuleFunction } from 'ts-eslint';
+import * as util from '../util';
+
+export type Options = util.InferOptionsTypeFromRule;
+export type MessageIds = util.InferMessageIdsTypeFromRule;
+
+export default util.createRule({
+ name: 'semi',
+ meta: {
+ type: 'layout',
+ docs: {
+ description: 'Require or disallow semicolons instead of ASI',
+ category: 'Stylistic Issues',
+ recommended: false,
+ },
+ fixable: 'code',
+ schema: baseRule.meta.schema,
+ messages: baseRule.meta.messages,
+ },
+ defaultOptions: [
+ 'always',
+ {
+ omitLastInOneLineBlock: false,
+ beforeStatementContinuationChars: 'any',
+ },
+ ],
+ create(context) {
+ const rules = baseRule.create(context);
+ const checkForSemicolon = rules.ExpressionStatement as RuleFunction<
+ TSESTree.Node
+ >;
+
+ /*
+ The following nodes are handled by the member-delimiter-style rule
+ AST_NODE_TYPES.TSCallSignatureDeclaration,
+ AST_NODE_TYPES.TSConstructSignatureDeclaration,
+ AST_NODE_TYPES.TSIndexSignature,
+ AST_NODE_TYPES.TSMethodSignature,
+ AST_NODE_TYPES.TSPropertySignature,
+ */
+ const nodesToCheck = [
+ AST_NODE_TYPES.ClassProperty,
+ AST_NODE_TYPES.TSAbstractClassProperty,
+ AST_NODE_TYPES.TSAbstractMethodDefinition,
+ AST_NODE_TYPES.TSDeclareFunction,
+ AST_NODE_TYPES.TSExportAssignment,
+ AST_NODE_TYPES.TSImportEqualsDeclaration,
+ AST_NODE_TYPES.TSTypeAliasDeclaration,
+ ].reduce((acc, node) => {
+ acc[node] = checkForSemicolon;
+ return acc;
+ }, {});
+
+ return {
+ ...rules,
+ ...nodesToCheck,
+ ExportDefaultDeclaration(node) {
+ if (node.declaration.type !== AST_NODE_TYPES.TSInterfaceDeclaration) {
+ rules.ExportDefaultDeclaration(node);
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/rules/semi.test.ts b/packages/eslint-plugin/tests/rules/semi.test.ts
new file mode 100644
index 000000000000..65ee49917335
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/semi.test.ts
@@ -0,0 +1,119 @@
+import rule, { MessageIds, Options } from '../../src/rules/semi';
+import { InvalidTestCase, RuleTester, ValidTestCase } from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+});
+
+ruleTester.run('semi', rule, {
+ valid: [
+ `
+class Class {
+ prop: string;
+}
+ `,
+ `
+abstract class AbsClass {
+ abstract prop: string;
+ abstract meth(): string;
+}
+ `,
+ `declare function declareFn(): string;`,
+ `export default interface Foo {}`,
+ 'export = Foo;',
+ 'import f = require("f");',
+ 'type Foo = {};',
+ ].reduce[]>((acc, code) => {
+ acc.push({
+ code,
+ options: ['always'],
+ });
+ acc.push({
+ code: code.replace(/;/g, ''),
+ options: ['never'],
+ });
+
+ return acc;
+ }, []),
+ invalid: [
+ {
+ code: `
+class Class {
+ prop: string;
+}
+ `,
+ errors: [
+ {
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: `
+abstract class AbsClass {
+ abstract prop: string;
+ abstract meth(): string;
+}
+ `,
+ errors: [
+ {
+ line: 3,
+ },
+ {
+ line: 4,
+ },
+ ],
+ },
+ {
+ code: `declare function declareFn(): string;`,
+ errors: [
+ {
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'export = Foo;',
+ errors: [
+ {
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'import f = require("f");',
+ errors: [
+ {
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'type Foo = {};',
+ errors: [
+ {
+ line: 1,
+ },
+ ],
+ },
+ ].reduce[]>((acc, test) => {
+ acc.push({
+ code: test.code.replace(/;/g, ''),
+ options: ['always'],
+ errors: test.errors.map(e => ({
+ ...e,
+ message: 'Missing semicolon.',
+ })) as any,
+ });
+ acc.push({
+ code: test.code,
+ options: ['never'],
+ errors: test.errors.map(e => ({
+ ...e,
+ message: 'Extra semicolon.',
+ })) as any,
+ });
+
+ return acc;
+ }, []),
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index aca4c8d8638c..aac61ca5848a 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -353,3 +353,34 @@ declare module 'eslint/lib/rules/no-extra-parens' {
>;
export = rule;
}
+
+declare module 'eslint/lib/rules/semi' {
+ import { TSESTree } from '@typescript-eslint/typescript-estree';
+ import RuleModule from 'ts-eslint';
+
+ const rule: RuleModule<
+ never,
+ [
+ 'always' | 'never',
+ {
+ beforeStatementContinuationChars?: 'always' | 'any' | 'never';
+ omitLastInOneLineBlock?: boolean;
+ }?
+ ],
+ {
+ VariableDeclaration(node: TSESTree.VariableDeclaration): void;
+ ExpressionStatement(node: TSESTree.ExpressionStatement): void;
+ ReturnStatement(node: TSESTree.ReturnStatement): void;
+ ThrowStatement(node: TSESTree.ThrowStatement): void;
+ DoWhileStatement(node: TSESTree.DoWhileStatement): void;
+ DebuggerStatement(node: TSESTree.DebuggerStatement): void;
+ BreakStatement(node: TSESTree.BreakStatement): void;
+ ContinueStatement(node: TSESTree.ContinueStatement): void;
+ ImportDeclaration(node: TSESTree.ImportDeclaration): void;
+ ExportAllDeclaration(node: TSESTree.ExportAllDeclaration): void;
+ ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void;
+ ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration): void;
+ }
+ >;
+ export = rule;
+}
diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts
index ce6e6c577c28..3053c988d414 100644
--- a/packages/eslint-plugin/typings/ts-eslint.d.ts
+++ b/packages/eslint-plugin/typings/ts-eslint.d.ts
@@ -419,6 +419,7 @@ declare module 'ts-eslint' {
ClassBody?: RuleFunction;
ClassDeclaration?: RuleFunction;
ClassExpression?: RuleFunction;
+ ClassProperty?: RuleFunction;
Comment?: RuleFunction;
ConditionalExpression?: RuleFunction;
ContinueStatement?: RuleFunction;