Repository: exadel-inc/esl
Pull request #2510 (2024-07-12 17:47:51)

refactor(lint): fix deprecated-4/media-rule-list-parse rule according live testing notes and introduced utilities
Relates to: #2500


Patch:
diff --git a/eslint/src/core/check-version.ts b/eslint/src/core/check-version.ts
index b5447a547..be590963a 100644
--- a/eslint/src/core/check-version.ts
+++ b/eslint/src/core/check-version.ts
@@ -1,25 +1,23 @@
 import {lt} from 'semver';
-import color from 'kleur';
+import {log} from './log';
 
-const ESL_PACKAGE = '@exadel/esl';
-const PLUGIN_PACKAGE = '@exadel/eslint-plugin-esl';
+export const ESL_PACKAGE = '@exadel/esl';
+export const PLUGIN_PACKAGE = '@exadel/eslint-plugin-esl';
 
-function getInstalledVersion(packageName: string): string | null {
+export function getInstalledVersion(packageName: string): string {
   try {
     // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
     return require(`${packageName}/package.json`).version;
   } catch (error) {
-    return null;
+    return '';
   }
 }
 
-export function checkVersion(): void {
-  const eslintVersion = getInstalledVersion(PLUGIN_PACKAGE);
-  const eslVersion = getInstalledVersion(ESL_PACKAGE);
-  if  (!(eslintVersion && eslVersion && lt(eslintVersion, eslVersion))) return;
+export const ESL_PACKAGE_VERSION = getInstalledVersion(ESL_PACKAGE);
+export const PLUGIN_PACKAGE_VERSION = getInstalledVersion(PLUGIN_PACKAGE);
 
-  console.log(`\n${color.yellow('⚠️ Warning:')}
-  Your installed version of ${color.yellow(PLUGIN_PACKAGE)} (${color.red(eslintVersion)}) \
-is lower than version of main package ${color.yellow(ESL_PACKAGE)} (${color.green(eslVersion)}).
-  Please update ${color.yellow(PLUGIN_PACKAGE)} to the latest version ${color.green(eslVersion)}\n`);
+if (lt(PLUGIN_PACKAGE_VERSION, ESL_PACKAGE_VERSION)) {
+  log(`Your installed version of ${PLUGIN_PACKAGE} (${PLUGIN_PACKAGE_VERSION})\
+is lower than version of main package ${ESL_PACKAGE} (${ESL_PACKAGE_VERSION}).
+Please update ${PLUGIN_PACKAGE} to the latest version ${ESL_PACKAGE_VERSION}`, 'warn');
 }
diff --git a/eslint/src/core/deprecated-class-method.ts b/eslint/src/core/deprecated-class-method.ts
index b48720981..06a32f863 100644
--- a/eslint/src/core/deprecated-class-method.ts
+++ b/eslint/src/core/deprecated-class-method.ts
@@ -10,7 +10,7 @@ const meta: Rule.RuleModule['meta'] = {
   fixable: 'code'
 };
 
-export interface replacementMethodCfg {
+export interface ESLintReplacementMethodCfg {
   replacement?: string;
   message: string;
 }
@@ -21,7 +21,7 @@ export interface ESLintDeprecationStaticMethodCfg {
   /** Deprecated static method name */
   deprecatedMethod: string;
   /** Function that returns recommended method */
-  getReplacementMethod: (expression: ESTree.CallExpression) => replacementMethodCfg;
+  getReplacementMethod: (expression: ESTree.CallExpression) => ESLintReplacementMethodCfg | string;
 }
 
 type StaticMethodNode = ESTree.MemberExpression & Rule.NodeParentExtension;
@@ -46,11 +46,13 @@ function isDeprecatedMethod(node: StaticMethodNode, config: ESLintDeprecationSta
 }
 
 function handleCallExpression(node: StaticMethodNode, context: Rule.RuleContext, config: ESLintDeprecationStaticMethodCfg): void {
-  const {replacement, message} = config.getReplacementMethod(node.parent as ESTree.CallExpression);
+  const replCfg = config.getReplacementMethod(node.parent as ESTree.CallExpression);
+  const message = typeof replCfg === 'string' ? `${config.className}.${replCfg}` : replCfg.message;
+  const replacement = typeof replCfg === 'string' ? replCfg : replCfg.replacement;
 
   context.report({
     node,
-    message: `[ESL Lint]: Deprecated static method ${config.className}.${config.deprecatedMethod}, use ${config.className}.${message} instead`,
+    message: `[ESL Lint]: Deprecated static method ${config.className}.${config.deprecatedMethod}, use ${message} instead`,
     fix: replacement ? (fixer): Rule.Fix => fixer.replaceText(node.property, replacement) : undefined
   });
 }
diff --git a/eslint/src/core/log.ts b/eslint/src/core/log.ts
new file mode 100644
index 000000000..1879bb879
--- /dev/null
+++ b/eslint/src/core/log.ts
@@ -0,0 +1,29 @@
+import color from 'kleur';
+import type {Rule} from 'eslint';
+
+const LOGGERS = {
+  error: console.error,
+  warn: console.warn,
+  off: console.info,
+};
+const HEADERS = {
+  error: color.red('[ESL Lint Plugin] ❌ Error:'),
+  warn: color.yellow('[ESL Lint Plugin] ⚠️ Warning:'),
+  off: color.blue('[ESL Lint Plugin] ℹ️ Info:'),
+};
+
+export function log(msg: string, severity: 'error' | 'warn' | 'off' = 'off'): void {
+  LOGGERS[severity](`\n${HEADERS[severity]}\n${msg}`);
+}
+
+export function buildLoggingRule(msg: string, severity: 'error' | 'warn' | 'off' = 'off'): Rule.RuleModule {
+  log(msg, severity);
+  return {
+    meta: {
+      docs: {
+        description: msg,
+      }
+    },
+    create: (): Rule.RuleListener => ({})
+  };
+}
diff --git a/eslint/src/index.ts b/eslint/src/index.ts
index 02de6529c..9b4dea5a4 100644
--- a/eslint/src/index.ts
+++ b/eslint/src/index.ts
@@ -1,13 +1,12 @@
+import './core/check-version';
+
 import DEPRECATED_4_RULES from './rules/4/all.rules';
 import DEPRECATED_5_RULES from './rules/5/all.rules';
-import {checkVersion} from './core/check-version';
 
 import type {Rule} from 'eslint';
 
 export type logLevel = 'warn' | 'error';
 
-checkVersion();
-
 const buildDefault = (definition: Record<string, Rule.RuleModule>, level: logLevel): Record<string, logLevel> => {
   const config: Record<string, logLevel> = {};
   for (const name of Object.keys(definition)) {
diff --git a/eslint/src/rules/4/deprecated.media-rule-list-parse.ts b/eslint/src/rules/4/deprecated.media-rule-list-parse.ts
index 48e4b1255..2dc15e368 100644
--- a/eslint/src/rules/4/deprecated.media-rule-list-parse.ts
+++ b/eslint/src/rules/4/deprecated.media-rule-list-parse.ts
@@ -1,16 +1,28 @@
+import {lte} from 'semver';
+import {ESL_PACKAGE_VERSION} from '../../core/check-version';
+import {buildLoggingRule} from '../../core/log';
 import {buildRule} from '../../core/deprecated-class-method';
-import type {replacementMethodCfg} from '../../core/deprecated-class-method';
+
+import type * as ESTree from 'estree';
+import type {ESLintReplacementMethodCfg} from '../../core/deprecated-class-method';
+
+const AVAILABLE_SINCE = '5.0.0-beta.24';
+const isActual = lte(ESL_PACKAGE_VERSION, AVAILABLE_SINCE);
 
 /**
  * Rule for deprecated 'parse' method of {@link ESLMediaRuleList}
  */
-export default buildRule({
-  className: 'ESLMediaRuleList',
-  deprecatedMethod: 'parse',
-  getReplacementMethod: (expression): replacementMethodCfg => {
-    const args = expression.arguments;
-    if (expression.type !== 'CallExpression') return {message: 'parseQuery or parseTuple'};
-    const methodName = args.length === 1 || (args[1]?.type !== 'Literal' && args[1]?.type !== 'TemplateLiteral') ? 'parseQuery' : 'parseTuple';
-    return {message: methodName, replacement: methodName};
-  }
-});
+export default isActual ?
+  buildRule({
+    className: 'ESLMediaRuleList',
+    deprecatedMethod: 'parse',
+    getReplacementMethod: (expression): ESLintReplacementMethodCfg | string => {
+      const args = expression.arguments;
+      const isLiteral = (node: ESTree.Expression | ESTree.SpreadElement): boolean => node?.type === 'Literal' || node?.type === 'TemplateLiteral';
+      if (expression.type === 'CallExpression' && args.length === 1) return 'parseQuery';
+      if (expression.type === 'CallExpression' && args.length === 2 && isLiteral(args[1])) return 'parseTuple';
+      if (expression.type === 'CallExpression' && args.length === 3) return 'parseTuple';
+      return {message: 'ESLMediaRuleList.parseQuery or ESLMediaRuleList.parseTuple'};
+    }
+  }) :
+  buildLoggingRule(`'ESLMediaRuleList.parse' was updated in v${AVAILABLE_SINCE}. Rule 'deprecated-4/media-rule-list-parse' is skipped.`);


Test patch:
diff --git a/eslint/test/deprecated-class-method.test.ts b/eslint/test/deprecated-class-method.test.ts
index 3d1ef0d70..bbc4cc217 100644
--- a/eslint/test/deprecated-class-method.test.ts
+++ b/eslint/test/deprecated-class-method.test.ts
@@ -61,7 +61,7 @@ const INVALID_CASES_TEST_CLASS = [
       TestClass.oldMethod();
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodNoArgs instead'
+      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use newMethodNoArgs instead'
     ],
     output: `
       TestClass.oldMethod();
@@ -71,7 +71,7 @@ const INVALID_CASES_TEST_CLASS = [
       TestClass.oldMethod(1, () => {});
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodMultipleArgsNonLiteral instead'
+      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use newMethodMultipleArgsNonLiteral instead'
     ],
     output: `
       TestClass.newMethodMultipleArgsNonLiteral(1, () => {});
@@ -81,7 +81,7 @@ const INVALID_CASES_TEST_CLASS = [
       TestClass.oldMethod('test');
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodOneArg instead'
+      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use newMethodOneArg instead'
     ],
     output: `
       TestClass.newMethodOneArg('test');
@@ -91,7 +91,7 @@ const INVALID_CASES_TEST_CLASS = [
       TestClass.oldMethod('test', 42);
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodMultipleArgs instead'
+      '[ESL Lint]: Deprecated static method TestClass.oldMethod, use newMethodMultipleArgs instead'
     ],
     output: `
       TestClass.newMethodMultipleArgs('test', 42);
@@ -105,7 +105,7 @@ const INVALID_CASES_RULE_LIST = [
       const t = ESLMediaRuleList.parse;
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or parseTuple instead'
+      '[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or ESLMediaRuleList.parseTuple instead'
     ],
     output: `
       const t = ESLMediaRuleList.parse;
@@ -115,7 +115,7 @@ const INVALID_CASES_RULE_LIST = [
       ESLMediaRuleList.parse;
     `,
     errors: [
-      '[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or parseTuple instead'
+      '[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or ESLMediaRuleList.parseTuple instead'
     ],
     output: `
       ESLMediaRuleList.parse;
@@ -130,16 +130,6 @@ const INVALID_CASES_RULE_LIST = [
     output: `
       ESLMediaRuleList.parseQuery('1 | 2');
     `
-  }, {
-    code: `
-      ESLMediaRuleList.parse('1 | 2', String);
-    `,
-    errors: [
-      '[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery instead'
-    ],
-    output: `
-      ESLMediaRuleList.parseQuery('1 | 2', String);
-    `
   }, {
     code: `
       ESLMediaRuleList.parse('1 | 2', '3|4');