Skip to content

Commit

Permalink
feat(lint): deprecation rule for ESlMediaRuleList.parse (#2509)
Browse files Browse the repository at this point in the history
  • Loading branch information
fshovchko committed Jul 12, 2024
1 parent 07b7e0e commit a1f916a
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 14 deletions.
2 changes: 2 additions & 0 deletions eslint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ The ESLint plugin provides a separate rule for each deprecated utility within th
- `@exadel/esl/deprecated-4/toggleable-action-params` - Rule for deprecated `ToggleableActionParams` alias for `ESLToggleableActionParams`.
- `@exadel/esl/deprecated-4/tooltip-action-params` - Rule for deprecated `TooltipActionParams` alias for `ESLTooltipActionParams`.

- `@exadel/esl/deprecated-4/media-rule-list-parse` - Rule for deprecated `ESLMediaRuleList.parse` alias for `ESLMediaRuleList.parseQuery` or `ESLMediaRuleList.parseTuple`.

- `@exadel/esl/deprecated-4/base-decorators-path` - Rule for deprecated `@attr`, `@prop`, `@boolAttr`, `@jsonAttr`, `@listen` import paths.

- `@exadel/esl/deprecated-5/alert-action-params` - Rule for deprecated `AlertActionParams` alias for `ESLAlertActionParams`.
Expand Down
56 changes: 56 additions & 0 deletions eslint/src/core/deprecated-class-method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type * as ESTree from 'estree';
import type {Rule} from 'eslint';

const meta: Rule.RuleModule['meta'] = {
type: 'suggestion',
docs: {
description: 'replace deprecated class static methods with recommended ones',
recommended: true
},
fixable: 'code'
};

export interface replacementMethodCfg {
replacement?: string;
message: string;
}

export interface ESLintDeprecationStaticMethodCfg {
/** Class name */
className: string;
/** Deprecated static method name */
deprecatedMethod: string;
/** Function that returns recommended method */
getReplacemetMethod: (expression: ESTree.CallExpression) => replacementMethodCfg;
}

type StaticMethodNode = ESTree.MemberExpression & Rule.NodeParentExtension;

/** Builds deprecation rule from {@link ESLintDeprecationStaticMethodCfg} object */
export function buildRule(config: ESLintDeprecationStaticMethodCfg): Rule.RuleModule {
const create = (context: Rule.RuleContext): Rule.RuleListener => {
return {
MemberExpression(node: StaticMethodNode): null {
if (isDeprecatedMethod(node, config)) handleCallExpression(node, context, config);
return null;
}
};
};

return {meta, create};
}

function isDeprecatedMethod(node: StaticMethodNode, config: ESLintDeprecationStaticMethodCfg): boolean {
const {object, property} = node;
return object.type === 'Identifier' && property.type === 'Identifier' && object.name === config.className && property.name === config.deprecatedMethod;
}

function handleCallExpression(node: StaticMethodNode, context: Rule.RuleContext, config: ESLintDeprecationStaticMethodCfg): void {
const {replacement, message} = config.getReplacemetMethod(node.parent as ESTree.CallExpression);

context.report({
node,
message: `[ESL Lint]: Deprecated static method ${config.className}.${config.deprecatedMethod}, use ${config.className}.${message} instead`,
fix: replacement ? (fixer): Rule.Fix => fixer.replaceText(node.property, replacement) : undefined
});
}
3 changes: 3 additions & 0 deletions eslint/src/rules/4/all.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import deprecatedToggleableActionParams from './deprecated.toggleable-action-par

import deprecatedBaseDecoratorsPath from './deprecated.base-decorators-path';

import deprecatedMediaRuleListParse from './deprecated.media-rule-list-parse';

export default {
// Aliases
'deprecated-4/generate-uid': deprecatedGenerateUid,
'deprecated-4/deep-compare': deprecatedDeepCompare,
'deprecated-4/event-utils': deprecatedEventUtils,
'deprecated-4/traversing-query': deprecatedTraversingQuery,
'deprecated-4/toggleable-action-params': deprecatedToggleableActionParams,
'deprecated-4/media-rule-list-parse': deprecatedMediaRuleListParse,
// Paths
'deprecated-4/base-decorators-path': deprecatedBaseDecoratorsPath
};
16 changes: 16 additions & 0 deletions eslint/src/rules/4/deprecated.media-rule-list-parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {buildRule} from '../../core/deprecated-class-method';
import type {replacementMethodCfg} from '../../core/deprecated-class-method';

/**
* Rule for deprecated 'parse' method of {@link ESLMediaRuleList}
*/
export default buildRule({
className: 'ESLMediaRuleList',
deprecatedMethod: 'parse',
getReplacemetMethod: (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};
}
});
209 changes: 209 additions & 0 deletions eslint/test/deprecated-class-method.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import {RuleTester} from 'eslint';
import {buildRule} from '../src/core/deprecated-class-method';

import deprecatedMediaRuleListParse from '../src/rules/4/deprecated.media-rule-list-parse';

const VALID_CASES = [
{
code: `
ESLMediaRuleList.parseQuery('1 | 2');
`
}, {
code: `
ESLMediaRuleList.parseQuery('1 | 2', String);
`
}, {
code: `
ESLMediaRuleList.parseTuple('1 | 2', '3|4');
`
}, {
code: `
ESLMediaRuleList.parseTuple('1 | 2', '3|4', String);
`
}, {
code: `
TestClass.newMethodNoArgs();
`
}, {
code: `
TestClass.newMethodOneArg('test');
`
}, {
code: `
TestClass.newMethodMultipleArgs('test', 42);
`
}, {
code: `
TestClass.newMethodMultipleArgsNonLiteral(1, 2);
`
}, {
code: `
AnotherClass.modernMethod();
`
}, {
code: `
AnotherClass.modernMethodForManyArgs(1, 2, 3);
`
}, {
code: `
AnotherClass.modernMethod;
`
}, {
code: `
const method = AnotherClass.modernMethod;
`
}
];

const INVALID_CASES_TEST_CLASS = [
{
code: `
TestClass.oldMethod();
`,
errors: [
'[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodNoArgs instead'
],
output: `
TestClass.oldMethod();
`
}, {
code: `
TestClass.oldMethod(1, () => {});
`,
errors: [
'[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodMultipleArgsNonLiteral instead'
],
output: `
TestClass.newMethodMultipleArgsNonLiteral(1, () => {});
`
}, {
code: `
TestClass.oldMethod('test');
`,
errors: [
'[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodOneArg instead'
],
output: `
TestClass.newMethodOneArg('test');
`
}, {
code: `
TestClass.oldMethod('test', 42);
`,
errors: [
'[ESL Lint]: Deprecated static method TestClass.oldMethod, use TestClass.newMethodMultipleArgs instead'
],
output: `
TestClass.newMethodMultipleArgs('test', 42);
`
}
];

const INVALID_CASES_RULE_LIST = [
{
code: `
const t = ESLMediaRuleList.parse;
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or parseTuple instead'
],
output: `
const t = ESLMediaRuleList.parse;
`
}, {
code: `
ESLMediaRuleList.parse;
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery or parseTuple instead'
],
output: `
ESLMediaRuleList.parse;
`
}, {
code: `
ESLMediaRuleList.parse('1 | 2');
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseQuery instead'
],
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');
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseTuple instead'
],
output: `
ESLMediaRuleList.parseTuple('1 | 2', '3|4');
`
}, {
code: `
ESLMediaRuleList.parse('1 | 2', \`3|4\`);
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseTuple instead'
],
output: `
ESLMediaRuleList.parseTuple('1 | 2', \`3|4\`);
`
}, {
code: `
ESLMediaRuleList.parse('1 | 2', '3|4', String);
`,
errors: [
'[ESL Lint]: Deprecated static method ESLMediaRuleList.parse, use ESLMediaRuleList.parseTuple instead'
],
output: `
ESLMediaRuleList.parseTuple('1 | 2', '3|4', String);
`
}
];

describe('ESL Migration Rules: Deprecated Static Method: valid', () => {
const rule = buildRule({
className: 'TestClass',
deprecatedMethod: 'oldMethod',
getReplacemetMethod: (expression) => {
const args = expression.arguments;
if (args.length === 0) return {message: 'newMethodNoArgs'};

let methodName;
if (args.length === 1) methodName = 'newMethodOneArg';
else if (args.length > 1 && args[args.length - 1].type !== 'Literal' && args[args.length - 1].type !== 'TemplateLiteral') {
methodName = 'newMethodMultipleArgsNonLiteral';
}
else methodName = 'newMethodMultipleArgs';
return {message: methodName, replacement: methodName};
}
});

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser')
});

ruleTester.run('deprecated-static-method', rule, {valid: VALID_CASES, invalid: INVALID_CASES_TEST_CLASS});
});

describe('ESL Migration Rules: Deprecated Static Method: valid', () => {
const rule = deprecatedMediaRuleListParse;

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser')
});

ruleTester.run('deprecated-static-method', rule, {valid: VALID_CASES, invalid: INVALID_CASES_RULE_LIST});
});
6 changes: 3 additions & 3 deletions src/modules/esl-image/core/esl-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ESLImage extends ESLBaseElement {
this.alt =
this.alt || this.getAttribute('aria-label') || this.getAttribute('data-alt') || '';
this.updateA11y();
this.srcRules = ESLMediaRuleList.parse(this.src);
this.srcRules = ESLMediaRuleList.parseQuery(this.src);
if (this.lazyObservable) {
this.removeAttribute('lazy-triggered');
getIObserver().observe(this);
Expand Down Expand Up @@ -100,7 +100,7 @@ export class ESLImage extends ESLBaseElement {
this.updateA11y();
break;
case 'data-src':
this.srcRules = ESLMediaRuleList.parse(newVal);
this.srcRules = ESLMediaRuleList.parseQuery(newVal);
this.refresh();
break;
case 'data-src-base':
Expand All @@ -117,7 +117,7 @@ export class ESLImage extends ESLBaseElement {

public get srcRules(): ESLMediaRuleList<string> {
if (!this._srcRules) {
this.srcRules = ESLMediaRuleList.parse(this.src);
this.srcRules = ESLMediaRuleList.parseQuery(this.src);
}
return this._srcRules;
}
Expand Down
12 changes: 6 additions & 6 deletions src/modules/esl-media-query/test/esl-media-rule-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('ESLMediaRuleList', () => {

describe('Integration cases:', () => {
test('Basic case: "1 | @sm => 2 | @md => 3" parsed correctly', () => {
const mrl = ESLMediaRuleList.parse('1 | @sm => 2 | @md => 3');
const mrl = ESLMediaRuleList.parseQuery('1 | @sm => 2 | @md => 3');
expect(mrl.rules.length).toBe(3);

mockSmMatchMedia.matches = false;
Expand All @@ -30,7 +30,7 @@ describe('ESLMediaRuleList', () => {
});

test('Extended media case parsed correctly: "1 | @sm and @md => 2"', () => {
const mrl = ESLMediaRuleList.parse('1 | @sm and @md => 2');
const mrl = ESLMediaRuleList.parseQuery('1 | @sm and @md => 2');
const listener = jest.fn();

expect(mrl.rules.length).toBe(2);
Expand All @@ -57,7 +57,7 @@ describe('ESLMediaRuleList', () => {
});

test('Extended media case parsed correctly: "1 | @sm or @md => 2"', () => {
const mrl = ESLMediaRuleList.parse('1 | @sm or @md => 2');
const mrl = ESLMediaRuleList.parseQuery('1 | @sm or @md => 2');
const listener = jest.fn();

expect(mrl.rules.length).toBe(2);
Expand Down Expand Up @@ -112,20 +112,20 @@ describe('ESLMediaRuleList', () => {

describe('Basic cases:', () => {
test('Single value parsed to the single "all" rule', () => {
const mrl = ESLMediaRuleList.parse('123');
const mrl = ESLMediaRuleList.parseQuery('123');
expect(mrl.rules.length).toBe(1);
expect(mrl.active.length).toBeGreaterThan(0);
expect(mrl.value).toBe('123');
expect(mrl.activeValue).toBe('123');
});

test('Single rule with media query "@sm => 1"', () => {
const mrl = ESLMediaRuleList.parse('@sm => 1');
const mrl = ESLMediaRuleList.parseQuery('@sm => 1');
expect(mrl.rules.length).toBe(1);
});

test('Single rule "@sm => 1" response to the matcher correctly', () => {
const mrl = ESLMediaRuleList.parse('@sm => 1');
const mrl = ESLMediaRuleList.parseQuery('@sm => 1');

mockSmMatchMedia.matches = false;
expect(mrl.value).toBe(undefined);
Expand Down
Loading

0 comments on commit a1f916a

Please sign in to comment.