Repository: exadel-inc/esl
Pull request #1374 (2022-12-09 12:35:21)

feat(eslint): create custom rule for deprecated alias
Initial PR for ESlint custom rule to find and replace deprecated aliases 

ala-n: @exadel-inc/esl-core-team, please be aware that this PR is opened against the /epic branch and is going to be merged soon to unlock related epic substories.


Patch:
diff --git a/.eslintignore b/.eslintignore
index 0af8d0a90..031a81707 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -8,3 +8,6 @@ node_modules
 # Generated surces
 /modules
 /polyfills
+
+# Tem Submodule
+/eslint
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 2d53f1a4b..1ab86f9d6 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -10,6 +10,7 @@ extends:
   - 'eslint:recommended'
   - 'plugin:import/typescript'
   - 'plugin:@typescript-eslint/recommended'
+  - 'plugin:@exadel/esl/default-4'
 
   # Library base set
   - './linting/codestyle.eslintrc.yml'
@@ -28,11 +29,12 @@ parserOptions:
 
 # Lint additional plugins
 plugins:
+  - 'tsdoc'
   - 'import'
   - 'sonarjs'
   - 'editorconfig'
   - '@typescript-eslint'
-  - 'eslint-plugin-tsdoc'
+  - '@exadel/esl'
 
 # Overall rules
 rules:
diff --git a/eslint/core/deprecated-alias.d.ts b/eslint/core/deprecated-alias.d.ts
new file mode 100644
index 000000000..2be64da5a
--- /dev/null
+++ b/eslint/core/deprecated-alias.d.ts
@@ -0,0 +1,11 @@
+import type {Rule} from 'eslint';
+
+export interface ESLintDeprecationCfg {
+  /** Current alias name */
+  alias: string;
+  /** Deprecated name */
+  deprecation: string;
+}
+
+/** Builds deprecation rule */
+export declare function buildRule(option: ESLintDeprecationCfg): Rule.RuleModule;
diff --git a/eslint/core/deprecated-alias.js b/eslint/core/deprecated-alias.js
new file mode 100644
index 000000000..9ab4a1742
--- /dev/null
+++ b/eslint/core/deprecated-alias.js
@@ -0,0 +1,133 @@
+'use strict';
+
+const traverse = require('eslint-traverse');
+
+const meta = {
+  type: 'suggestion',
+
+  docs: {
+    description: 'replace deprecated aliases',
+    recommended: true,
+  },
+  settings: {
+    'import/parsers': {
+      '@typescript-eslint/parser': ['.ts', '.js', '.tsx', '.jsx']
+    }
+  },
+  fixable: 'code'
+};
+
+/**
+ * Builds deprecation rule
+ * @param {object} option
+ * @param {string} option.alias - current name
+ * @param {string} option.deprecation - deprecated name
+ */
+module.exports.buildRule = function buildRule({alias, deprecation}) {
+  const create = (context) => ({
+    ImportSpecifier(node) {
+      const importedValue = node.imported;
+      if (importedValue.name === deprecation) {
+        context.report({
+          node,
+          message: `[ESL Lint]: Deprecated alias ${deprecation} for ${alias}`,
+          fix: buildFixer(node, context, alias)
+        });
+      }
+      return null;
+    }
+  });
+  return {meta, create};
+};
+
+/**
+ * @param {object} context - AST tree object
+ * @param {AST.Token.Node} node - import node to process
+ * @param {string} alias - current name
+ */
+function buildFixer(node, context, alias) {
+  return (fixer) => {
+    const ranges = getIdentifierRanges(node, context);
+    return ranges.map(range => fixer.replaceTextRange(range, alias));
+  }
+}
+
+/**
+ * @param {object} context - AST tree object
+ * @param {object} importNode - import node to process
+ * @returns {[number, number][]}
+ */
+function getIdentifierRanges(importNode, context) {
+  const root = getRoot(importNode);
+  const {name} = importNode.imported;
+  const identifiers = collectIdentifiers(context, root, name);
+
+  const overrides = [];
+  const occurrences = new Set();
+
+  for (const idNode of identifiers) {
+    const {parent} = idNode;
+    if (parent.type === 'MemberExpression') {
+      if (parent.object.name === name) occurrences.add(parent.object);
+    } else if (parent.type === 'VariableDeclarator') {
+      overrides.push(parent);
+    } else {
+      occurrences.add(idNode);
+    }
+  }
+
+  for (const declaration of overrides) {
+    const scope = getScopeNode(declaration);
+    if (!scope) continue;
+    const nestedNodes = collectIdentifiers(context, scope, name);
+    for (const node of nestedNodes) {
+      if (node.parent.type !== 'ImportSpecifier') {
+        occurrences.delete(node);
+      }
+    }
+    const initExpNodes = collectIdentifiers(context, declaration.init, name);
+    for (const node of initExpNodes) {
+      occurrences.add(node);
+    }
+  }
+
+  return getRanges(occurrences);
+}
+
+function collectIdentifiers(context, root, alias) {
+  const identifiers = [];
+  traverse(context, root, (path) => {
+    if (path.node.type !== 'Identifier' || path.node?.name !== alias) return;
+    identifiers.push(path.node);
+  });
+  return identifiers;
+}
+
+function getScopeNode(declaration) {
+  let node = declaration.parent;
+  if (!node) return null;
+  const isBlockScoped = node.kind && (node.kind === 'const' || node.kind === 'let');
+  while (node.parent !== null) {
+    node = node.parent;
+    if (node.type === 'BlockStatement' && (isBlockScoped || node.parent?.type === 'FunctionExpression')) return node;
+  }
+  return node;
+}
+
+function getRanges(nodes) {
+  const uniqNodes = [];
+  for (const node of nodes) {
+    if (!uniqNodes.some((item) => String(item.range) === String(node.range))) {
+      uniqNodes.push(node);
+    }
+  }
+  return uniqNodes.map((node) => node.range);
+}
+
+/** Finds the root node in the tree */
+function getRoot(node) {
+  while (node.parent !== null) {
+    node = node.parent;
+  }
+  return node;
+}
diff --git a/eslint/index.js b/eslint/index.js
new file mode 100644
index 000000000..018327e68
--- /dev/null
+++ b/eslint/index.js
@@ -0,0 +1,31 @@
+const DEPRECATED_4_RULES = {
+  'deprecated-4/generate-uid': require('./rules/4/deprecated.generate-uid'),
+  'deprecated-4/deep-compare': require('./rules/4/deprecated.deep-compare'),
+  'deprecated-4/event-utils': require('./rules/4/deprecated.event-utils'),
+  'deprecated-4/traversing-query': require('./rules/4/deprecated.traversing-query'),
+  'deprecated-4/toggleable-action-params': require('./rules/4/deprecated.toggleable-action-params')
+};
+
+const buildDefault = (definition, level) => {
+  const config = {};
+  for (const name of Object.keys(definition)) {
+    const rule = `@exadel/esl/${name}`;
+    config[rule] = level;
+  }
+  return config;
+};
+
+exports.rules = Object.assign({}, DEPRECATED_4_RULES);
+
+exports.configs = {
+  'default-4': {
+    rules: {
+      ...buildDefault(DEPRECATED_4_RULES, 'warn')
+    }
+  },
+  'default-5': {
+    rules: {
+      ...buildDefault(DEPRECATED_4_RULES, 'error')
+    }
+  }
+};
diff --git a/eslint/jest.config.js b/eslint/jest.config.js
new file mode 100644
index 000000000..967a11efe
--- /dev/null
+++ b/eslint/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+  transform: {
+    '^.+\\.tsx?$': 'ts-jest'
+  },
+  testEnvironment: 'jsdom',
+  testRegex: '/test/(.+)\\.test\\.ts$',
+  moduleFileExtensions: ['ts', 'js', 'json']
+};
diff --git a/eslint/package.json b/eslint/package.json
new file mode 100644
index 000000000..43c949725
--- /dev/null
+++ b/eslint/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "@exadel/eslint-plugin-esl",
+  "version": "4.12.0",
+  "license": "MIT",
+  "description": "Helper ESLint rules to find and migrate ESL (@exadel/esl) library deprecations",
+  "main": "index.js",
+  "scripts": {
+    "test": "jest",
+    "version": "node ../build/bump.js"
+  },
+  "dependencies": {
+    "eslint-traverse": "^1.0.0"
+  },
+  "peerDependencies": {
+    "@exadel/esl": "^4.12.0",
+    "eslint": ">=8.0.0"
+  },
+  "keywords": [
+    "eslint",
+    "eslint-plugin",
+    "esl"
+  ],
+  "eslint": [
+    {
+      "name": "@exadel/eslint-plugin-esl",
+      "version": "4.12.0",
+      "main": "index.js"
+    }
+  ]
+}
diff --git a/eslint/rules/4/deprecated.deep-compare.js b/eslint/rules/4/deprecated.deep-compare.js
new file mode 100644
index 000000000..8879ffc51
--- /dev/null
+++ b/eslint/rules/4/deprecated.deep-compare.js
@@ -0,0 +1,9 @@
+const {buildRule} = require('../../core/deprecated-alias');
+
+/**
+ * Rule for deprecated `deepCompare` alias for {@link isEqual}
+ */
+module.exports = buildRule({
+  alias: 'isEqual',
+  deprecation: 'deepCompare',
+});
diff --git a/eslint/rules/4/deprecated.event-utils.js b/eslint/rules/4/deprecated.event-utils.js
new file mode 100644
index 000000000..8c60d89f8
--- /dev/null
+++ b/eslint/rules/4/deprecated.event-utils.js
@@ -0,0 +1,9 @@
+const {buildRule} = require('../../core/deprecated-alias');
+
+/**
+ * Rule for deprecated `EventUtils` alias for {@link ESLEventUtils}
+ */
+module.exports = buildRule({
+  alias: 'ESLEventUtils',
+  deprecation: 'EventUtils',
+});
diff --git a/eslint/rules/4/deprecated.generate-uid.js b/eslint/rules/4/deprecated.generate-uid.js
new file mode 100644
index 000000000..65889ec05
--- /dev/null
+++ b/eslint/rules/4/deprecated.generate-uid.js
@@ -0,0 +1,9 @@
+const {buildRule} = require('../../core/deprecated-alias');
+
+/**
+ * Rule for deprecated `generateUId` alias for {@link randUID}
+ */
+module.exports = buildRule({
+  alias: 'randUID',
+  deprecation: 'generateUId',
+});
diff --git a/eslint/rules/4/deprecated.toggleable-action-params.js b/eslint/rules/4/deprecated.toggleable-action-params.js
new file mode 100644
index 000000000..7744a41eb
--- /dev/null
+++ b/eslint/rules/4/deprecated.toggleable-action-params.js
@@ -0,0 +1,9 @@
+const {buildRule} = require('../../core/deprecated-alias');
+
+/**
+ * Rule for deprecated `ToggleableActionParams` alias for {@link ESLToggleableActionParams}
+ */
+module.exports = buildRule({
+  alias: 'ESLToggleableActionParams',
+  deprecation: 'ToggleableActionParams',
+});
diff --git a/eslint/rules/4/deprecated.traversing-query.js b/eslint/rules/4/deprecated.traversing-query.js
new file mode 100644
index 000000000..af0840e58
--- /dev/null
+++ b/eslint/rules/4/deprecated.traversing-query.js
@@ -0,0 +1,9 @@
+const {buildRule} = require('../../core/deprecated-alias');
+
+/**
+ * Rule for deprecated `TraversingQuery` alias for {@link ESLTraversingQuery}
+ */
+module.exports = buildRule({
+  alias: 'ESLTraversingQuery',
+  deprecation: 'TraversingQuery',
+});
diff --git a/jest.config.js b/jest.config.js
index d9fa253a3..0e6e0cb98 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -22,6 +22,8 @@ module.exports = {
     '!src/modules/*.ts',
     '!src/modules/*/*.ts',
     // libs exclude
-    '!**/node_modules/**'
+    '!**/node_modules/**',
+    // migration lint rules
+    'eslint/**'
   ]
 };
diff --git a/package-lock.json b/package-lock.json
index e8d667805..5c878743a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,8 @@
       "version": "4.12.0",
       "license": "MIT",
       "workspaces": [
-        "pages"
+        "pages",
+        "eslint"
       ],
       "dependencies": {
         "@types/video.js": "7.3.52",
@@ -18,6 +19,7 @@
       "devDependencies": {
         "@commitlint/cli": "^17.7.1",
         "@commitlint/config-conventional": "^17.7.0",
+        "@exadel/eslint-plugin-esl": "file:eslint",
         "@semantic-release/changelog": "^6.0.3",
         "@semantic-release/commit-analyzer": "^11.0.0",
         "@semantic-release/git": "^10.0.1",
@@ -54,6 +56,18 @@
         "typescript": "5.2.2"
       }
     },
+    "eslint": {
+      "name": "@exadel/eslint-plugin-esl",
+      "version": "4.12.0",
+      "license": "MIT",
+      "dependencies": {
+        "eslint-traverse": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@exadel/esl": "^4.12.0",
+        "eslint": ">=8.0.0"
+      }
+    },
     "node_modules/@11ty/dependency-tree": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz",
@@ -219,7 +233,6 @@
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
       "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -1377,7 +1390,6 @@
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
       "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
-      "dev": true,
       "dependencies": {
         "eslint-visitor-keys": "^3.3.0"
       },
@@ -1392,7 +1404,6 @@
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz",
       "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==",
-      "dev": true,
       "engines": {
         "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
       }
@@ -1401,7 +1412,6 @@
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
       "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
-      "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
@@ -1424,7 +1434,6 @@
       "version": "8.49.0",
       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
       "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
-      "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
@@ -1437,11 +1446,14 @@
       "resolved": "pages",
       "link": true
     },
+    "node_modules/@exadel/eslint-plugin-esl": {
+      "resolved": "eslint",
+      "link": true
+    },
     "node_modules/@humanwhocodes/config-array": {
       "version": "0.11.11",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
       "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
-      "dev": true,
       "dependencies": {
         "@humanwhocodes/object-schema": "^1.2.1",
         "debug": "^4.1.1",
@@ -1455,7 +1467,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
       "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
-      "dev": true,
       "engines": {
         "node": ">=12.22"
       },
@@ -1467,8 +1478,7 @@
     "node_modules/@humanwhocodes/object-schema": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
-      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
-      "dev": true
+      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
     },
     "node_modules/@iarna/toml": {
       "version": "2.2.5",
@@ -3987,7 +3997,6 @@
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true,
       "peerDependencies": {
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
@@ -4080,7 +4089,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       }
@@ -4588,7 +4596,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true,
       "engines": {
         "node": ">=6"
       }
@@ -5804,8 +5811,7 @@
     "node_modules/deep-is": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
-      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "dev": true
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
     },
     "node_modules/deepmerge": {
       "version": "4.3.1",
@@ -5909,7 +5915,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
       "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
       "dependencies": {
         "esutils": "^2.0.2"
       },
@@ -6511,7 +6516,6 @@
       "version": "8.49.0",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
       "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
-      "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
@@ -6717,11 +6721,15 @@
         "node": ">=8.0.0"
       }
     },
+    "node_modules/eslint-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-traverse/-/eslint-traverse-1.0.0.tgz",
+      "integrity": "sha512-bSp37rQs93LF8rZ409EI369DGCI4tELbFVmFNxI6QbuveS7VRxYVyUhwDafKN/enMyUh88HQQ7ZoGUHtPuGdcw=="
+    },
     "node_modules/eslint-visitor-keys": {
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
       "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-      "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       },
@@ -6733,7 +6741,6 @@
       "version": "7.2.2",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
       "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
-      "dev": true,
       "dependencies": {
         "esrecurse": "^4.3.0",
         "estraverse": "^5.2.0"
@@ -6749,7 +6756,6 @@
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
       "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true,
       "engines": {
         "node": ">=4.0"
       }
@@ -6758,7 +6764,6 @@
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
       "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
-      "dev": true,
       "dependencies": {
         "is-glob": "^4.0.3"
       },
@@ -6770,7 +6775,6 @@
       "version": "9.6.1",
       "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
       "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
-      "dev": true,
       "dependencies": {
         "acorn": "^8.9.0",
         "acorn-jsx": "^5.3.2",
@@ -6799,7 +6803,6 @@
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz",
       "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==",
-      "dev": true,
       "dependencies": {
         "estraverse": "^5.1.0"
       },
@@ -6811,7 +6814,6 @@
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
       "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true,
       "engines": {
         "node": ">=4.0"
       }
@@ -6847,7 +6849,6 @@
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -6968,8 +6969,7 @@
     "node_modules/fast-levenshtein": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-      "dev": true
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
     },
     "node_modules/fastest-levenshtein": {
       "version": "1.0.16",
@@ -7022,7 +7022,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
       "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
-      "dev": true,
       "dependencies": {
         "flat-cache": "^3.0.4"
       },
@@ -7072,7 +7071,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
       "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-      "dev": true,
       "dependencies": {
         "locate-path": "^6.0.0",
         "path-exists": "^4.0.0"
@@ -7103,7 +7101,6 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
       "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
-      "dev": true,
       "dependencies": {
         "flatted": "^3.1.0",
         "rimraf": "^3.0.2"
@@ -7116,7 +7113,6 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
       "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "dev": true,
       "dependencies": {
         "glob": "^7.1.3"
       },
@@ -7130,8 +7126,7 @@
     "node_modules/flatted": {
       "version": "3.2.5",
       "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
-      "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
-      "dev": true
+      "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg=="
     },
     "node_modules/for-each": {
       "version": "0.3.3",
@@ -7631,7 +7626,6 @@
       "version": "13.21.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
       "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
-      "dev": true,
       "dependencies": {
         "type-fest": "^0.20.2"
       },
@@ -7703,8 +7697,7 @@
     "node_modules/graphemer": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
-      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
-      "dev": true
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
     },
     "node_modules/gray-matter": {
       "version": "4.0.3",
@@ -8088,7 +8081,6 @@
       "version": "5.2.4",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
       "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
-      "dev": true,
       "engines": {
         "node": ">= 4"
       }
@@ -8111,7 +8103,6 @@
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
       "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "dev": true,
       "dependencies": {
         "parent-module": "^1.0.0",
         "resolve-from": "^4.0.0"
@@ -8127,7 +8118,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true,
       "engines": {
         "node": ">=4"
       }
@@ -8175,7 +8165,6 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
       "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
-      "dev": true,
       "engines": {
         "node": ">=0.8.19"
       }
@@ -8528,7 +8517,6 @@
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
       "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       }
@@ -9540,8 +9528,7 @@
     "node_modules/json-stable-stringify-without-jsonify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
-      "dev": true
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
     },
     "node_modules/json-stringify-safe": {
       "version": "5.0.1",
@@ -9757,7 +9744,6 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
       "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
-      "dev": true,
       "dependencies": {
         "prelude-ls": "^1.2.1",
         "type-check": "~0.4.0"
@@ -10262,7 +10248,6 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
       "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-      "dev": true,
       "dependencies": {
         "p-locate": "^5.0.0"
       },
@@ -10361,8 +10346,7 @@
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
-      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
-      "dev": true
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
     },
     "node_modules/lodash.mergewith": {
       "version": "4.6.2",
@@ -10974,8 +10958,7 @@
     "node_modules/natural-compare": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
-      "dev": true
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
     },
     "node_modules/needle": {
       "version": "3.1.0",
@@ -14633,7 +14616,6 @@
       "version": "0.9.3",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
       "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
-      "dev": true,
       "dependencies": {
         "@aashutoshrathi/word-wrap": "^1.2.3",
         "deep-is": "^0.1.3",
@@ -14782,7 +14764,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
       "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-      "dev": true,
       "dependencies": {
         "yocto-queue": "^0.1.0"
       },
@@ -14797,7 +14778,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
       "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-      "dev": true,
       "dependencies": {
         "p-limit": "^3.0.2"
       },
@@ -14907,7 +14887,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
       "dependencies": {
         "callsites": "^3.0.0"
       },
@@ -15372,7 +15351,6 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
-      "dev": true,
       "engines": {
         "node": ">= 0.8.0"
       }
@@ -17413,7 +17391,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "dependencies": {
         "ansi-regex": "^5.0.1"
       },
@@ -17476,7 +17453,6 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
       "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       },
@@ -18126,8 +18102,7 @@
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
-      "dev": true
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
     },
     "node_modules/through": {
       "version": "2.3.8",
@@ -18436,7 +18411,6 @@
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
       "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
-      "dev": true,
       "dependencies": {
         "prelude-ls": "^1.2.1"
       },
@@ -18457,7 +18431,6 @@
       "version": "0.20.2",
       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
       "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
-      "dev": true,
       "engines": {
         "node": ">=10"
       },
@@ -19356,7 +19329,6 @@
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-      "dev": true,
       "engines": {
         "node": ">=10"
       },
diff --git a/package.json b/package.json
index 624d71450..785efd6af 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,8 @@
     "src/polyfills/**/*.ts"
   ],
   "workspaces": [
-    "pages"
+    "pages",
+    "eslint"
   ],
   "scripts": {
     "start": "cd pages && npm run start",
@@ -84,6 +85,7 @@
   "devDependencies": {
     "@commitlint/cli": "^17.7.1",
     "@commitlint/config-conventional": "^17.7.0",
+    "@exadel/eslint-plugin-esl": "file:eslint",
     "@semantic-release/changelog": "^6.0.3",
     "@semantic-release/commit-analyzer": "^11.0.0",
     "@semantic-release/git": "^10.0.1",


Test patch:
diff --git a/eslint/test/deprecated-alias.invalid.test.ts b/eslint/test/deprecated-alias.invalid.test.ts
new file mode 100644
index 000000000..73bc7e888
--- /dev/null
+++ b/eslint/test/deprecated-alias.invalid.test.ts
@@ -0,0 +1,191 @@
+import {RuleTester} from 'eslint';
+import {buildRule} from '../core/deprecated-alias';
+
+const INVALID_CASES = [
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName as FN} from '../rules/4.0.0/test';
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName as FN} from '../rules/4.0.0/test';
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = params.smth;
+        const instance = new DeprecatedClassName();
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = params.smth;
+        const instance = new DeprecatedClassName();
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        params.DeprecatedClassName = params.smth;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        params.DeprecatedClassName = params.smth;
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = DeprecatedClassName;
+      }
+      if (params) {
+        let DeprecatedClassName = DeprecatedClassName;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = AllowedClassName;
+      }
+      if (params) {
+        let DeprecatedClassName = AllowedClassName;
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        var DeprecatedClassName = DeprecatedClassName;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        var DeprecatedClassName = AllowedClassName;
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        return DeprecatedClassName;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        return AllowedClassName;
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = Array;
+        const instance = new DeprecatedClassName;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      if (params) {
+        const DeprecatedClassName = Array;
+        const instance = new DeprecatedClassName;
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      if (DeprecatedClassName.test()) {
+         // Hi
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      if (AllowedClassName.test()) {
+         // Hi
+      }
+    `
+  },
+  {
+    code: `
+      import {DeprecatedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        const DeprecatedClassName = Array;
+        const instance = new DeprecatedClassName;
+        return instance == DeprecatedClassName ? DeprecatedClassName : instance;
+      }
+    `,
+    errors: [
+      '[ESL Lint]: Deprecated alias DeprecatedClassName for AllowedClassName'
+    ],
+    output: `
+      import {AllowedClassName} from '../rules/4.0.0/test';
+      function f(params) {
+        const DeprecatedClassName = Array;
+        const instance = new DeprecatedClassName;
+        return instance == DeprecatedClassName ? DeprecatedClassName : instance;
+      }
+    `
+  }
+];
+
+// Test Runner
+describe('ESL Migration Rules: Deprecated Alias: invalid', () => {
+  const rule = buildRule({
+    alias: 'AllowedClassName',
+    deprecation: 'DeprecatedClassName',
+  });
+
+  const ruleTester = new RuleTester({
+    parser: require.resolve('@typescript-eslint/parser')
+  });
+
+  ruleTester.run('../base-rules/deprecated-alias', rule, {valid: [], invalid: INVALID_CASES});
+});
diff --git a/eslint/test/deprecated-alias.valid.test.ts b/eslint/test/deprecated-alias.valid.test.ts
new file mode 100644
index 000000000..9c6f78afe
--- /dev/null
+++ b/eslint/test/deprecated-alias.valid.test.ts
@@ -0,0 +1,77 @@
+import {RuleTester} from 'eslint';
+import {buildRule} from '../core/deprecated-alias';
+
+const VALID_CASES = [
+  {
+    code: `
+      import {AllowedClassName} from "../rules/4.0.0/test";
+    `
+  },
+  {
+    code: `
+      import {AllowedClassName as FN}  from '../rules/4.0.0/test';
+      FN.test();
+    `
+  },
+  {
+    code: `
+      import {AllowedClassName as FN}  from '../rules/4.0.0/test';
+      if (window.AllowedClassName) {  console.log('Hi'); }
+    `
+  },
+  {
+    code: `
+      import {AllowedClassName as FN}  from '../rules/4.0.0/test';
+      if (AllowedClassName.one() && FN.one('data')) { // Exception but not related
+        // Something
+      }
+    `
+  },
+  {
+    code: `
+      import {AllowedClassName}  from '../rules/4.0.0/test';
+
+      /** \`DeprecatedClassName\` is an alias for \`AllowedClassName\` */
+      export default function() {
+         // for DeprecatedClassName
+      }
+    `
+  },
+  {
+    code: `
+      const DeprecatedClassName = 1;
+      if (true) {
+        let DeprecatedClassName = 1;
+      }
+      if (true) {
+        const DeprecatedClassName = 1;
+      }
+      function f() {
+        var DeprecatedClassName = 1;
+      }
+    `
+  },
+  {
+    code: `
+      import DeprecatedClassName from '../rules/4.0.0/test';
+      if (params) {
+         const DeprecatedClassName = params.smth
+         params.DeprecatedClassName = new DeprecatedClassName();
+      }
+    `
+  }
+];
+
+// Test Runner
+describe('ESL Migration Rules: Deprecated Alias: valid', () => {
+  const rule = buildRule({
+    alias: 'AllowedClassName',
+    deprecation: 'DeprecatedClassName',
+  });
+
+  const ruleTester = new RuleTester({
+    parser: require.resolve('@typescript-eslint/parser')
+  });
+
+  ruleTester.run('../base-rules/deprecated-alias', rule, {valid: VALID_CASES, invalid: []});
+});