diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index cacb6666fddf..3807877a073e 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -128,6 +128,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e
| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | |
| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | |
| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | |
+| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | |
| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: |
| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | |
| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | |
diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md
index bd0480101cf1..5abf9e26d4ef 100644
--- a/packages/eslint-plugin/ROADMAP.md
+++ b/packages/eslint-plugin/ROADMAP.md
@@ -1,10 +1,15 @@
# Roadmap
-✅ (29) = done
+
+
+✅ (30) = done
🌟 (79) = in ESLint core
🔌 (33) = in another plugin
🌓 (16) = implementations differ or ESLint version is missing functionality
-🛑 (68) = unimplemented
+🛑 (68) = unimplemented
## TSLint rules
@@ -59,7 +64,7 @@
| [`no-empty`] | 🌟 | [`no-empty`][no-empty] |
| [`no-eval`] | 🌟 | [`no-eval`][no-eval] |
| [`no-floating-promises`] | 🛑 | N/A ([relevant plugin][plugin:promise]) |
-| [`no-for-in-array`] | 🛑 | N/A |
+| [`no-for-in-array`] | ✅ | [`@typescript-eslint/no-for-in-array`] |
| [`no-implicit-dependencies`] | 🔌 | [`import/no-extraneous-dependencies`] |
| [`no-inferred-empty-object-type`] | 🛑 | N/A |
| [`no-invalid-template-strings`] | 🌟 | [`no-template-curly-in-string`][no-template-curly-in-string] |
@@ -586,6 +591,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
[`@typescript-eslint/member-delimiter-style`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md
[`@typescript-eslint/prefer-interface`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md
[`@typescript-eslint/no-array-constructor`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md
+[`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md
diff --git a/packages/eslint-plugin/docs/rules/no-for-in-array.md b/packages/eslint-plugin/docs/rules/no-for-in-array.md
new file mode 100644
index 000000000000..3a1cf2bdd370
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-for-in-array.md
@@ -0,0 +1,44 @@
+# Disallow iterating over an array with a for-in loop (no-for-in-array)
+
+This rule prohibits iterating over an array with a for-in loop.
+
+## Rule Details
+
+Rationale from TSLint:
+
+A for-in loop (`for (var k in o)`) iterates over the properties of an Object.
+While it is legal to use for-in loops with array types, it is not common.
+for-in will iterate over the indices of the array as strings, omitting any "holes" in
+the array.
+More common is to use for-of, which iterates over the values of an array.
+If you want to iterate over the indices, alternatives include:
+
+```js
+array.forEach((value, index) => { ... });
+for (const [index, value] of array.entries()) { ... }
+for (let i = 0; i < array.length; i++) { ... }
+```
+
+Examples of **incorrect** code for this rule:
+
+```js
+for (const x in [3, 4, 5]) {
+ console.log(x);
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+for (const x in { a: 3, b: 4, c: 5 }) {
+ console.log(x);
+}
+```
+
+## When Not To Use It
+
+If you want to iterate through a loop using the indices in an array as strings, you can turn off this rule.
+
+## Related to
+
+- TSLint: ['no-for-in-array'](https://palantir.github.io/tslint/rules/no-for-in-array/)
diff --git a/packages/eslint-plugin/lib/rules/no-for-in-array.js b/packages/eslint-plugin/lib/rules/no-for-in-array.js
new file mode 100644
index 000000000000..23a19732ac45
--- /dev/null
+++ b/packages/eslint-plugin/lib/rules/no-for-in-array.js
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Disallow iterating over an array with a for-in loop
+ * @author Benjamin Lichtman
+ */
+'use strict';
+const ts = require('typescript');
+const util = require('../util');
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * @type {import("eslint").Rule.RuleModule}
+ */
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Disallow iterating over an array with a for-in loop',
+ category: 'Functionality',
+ recommended: false,
+ extraDescription: [util.tslintRule('no-for-in-array')],
+ url: util.metaDocsUrl('no-for-in-array')
+ },
+ fixable: null,
+ messages: {
+ forInViolation:
+ 'For-in loops over arrays are forbidden. Use for-of or array.forEach instead.'
+ },
+ schema: [],
+ type: 'problem'
+ },
+
+ create(context) {
+ return {
+ ForInStatement(node) {
+ const parserServices = util.getParserServices(context);
+ const checker = parserServices.program.getTypeChecker();
+ const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
+
+ const type = checker.getTypeAtLocation(originalNode.expression);
+
+ if (
+ (typeof type.symbol !== 'undefined' &&
+ type.symbol.name === 'Array') ||
+ (type.flags & ts.TypeFlags.StringLike) !== 0
+ ) {
+ context.report({
+ node,
+ messageId: 'forInViolation'
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/packages/eslint-plugin/lib/util.js b/packages/eslint-plugin/lib/util.js
index dac41fe21bb7..6714e600ee65 100644
--- a/packages/eslint-plugin/lib/util.js
+++ b/packages/eslint-plugin/lib/util.js
@@ -109,7 +109,7 @@ exports.upperCaseFirst = str => str[0].toUpperCase() + str.slice(1);
/**
* Try to retrieve typescript parser service from context
* @param {RuleContext} context Rule context
- * @returns {{esTreeNodeToTSNodeMap}|{program}|Object|*} parserServices
+ * @returns {{program: Program, esTreeNodeToTSNodeMap: NodeMap}} parserServices
*/
exports.getParserServices = context => {
if (
diff --git a/packages/eslint-plugin/tests/lib/rules/no-for-in-array.js b/packages/eslint-plugin/tests/lib/rules/no-for-in-array.js
new file mode 100644
index 000000000000..91984453000d
--- /dev/null
+++ b/packages/eslint-plugin/tests/lib/rules/no-for-in-array.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Disallow iterating over an array with a for-in loop
+ * @author Benjamin Lichtman
+ */
+'use strict';
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-for-in-array'),
+ RuleTester = require('eslint').RuleTester,
+ path = require('path');
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const rootDir = path.join(process.cwd(), 'tests/fixtures/');
+const parserOptions = {
+ ecmaVersion: 2015,
+ tsconfigRootDir: rootDir,
+ project: './tsconfig.json'
+};
+const ruleTester = new RuleTester({
+ parserOptions,
+ parser: '@typescript-eslint/parser'
+});
+
+ruleTester.run('no-for-in-array', rule, {
+ valid: [
+ `
+for (const x of [3, 4, 5]) {
+ console.log(x);
+}`,
+ `
+for (const x in { a: 1, b: 2, c: 3 }) {
+ console.log(x);
+}`
+ ],
+
+ invalid: [
+ {
+ code: `
+for (const x in [3, 4, 5]) {
+ console.log(x);
+}`,
+ errors: [
+ {
+ messageId: 'forInViolation',
+ type: 'ForInStatement'
+ }
+ ]
+ },
+ {
+ code: `
+const z = [3, 4, 5];
+for (const x in z) {
+ console.log(x);
+}`,
+ errors: [
+ {
+ messageId: 'forInViolation',
+ type: 'ForInStatement'
+ }
+ ]
+ }
+ ]
+});