Skip to content

Commit

Permalink
feat: add no-deprecated-props rule
Browse files Browse the repository at this point in the history
  • Loading branch information
kelsos committed Jan 23, 2024
1 parent 2b78b71 commit 0a498ba
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 0 deletions.
52 changes: 52 additions & 0 deletions docs/rules/no-deprecated-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: '@rotki/no-deprecated-props'
description: ...
since: v0.2.0
---

# @rotki/no-deprecated-props

> ...
- :black_nib:️ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

This rule reports the usage of deprecated properties in components.

<eslint-code-block fix>

<!-- eslint-skip -->

```vue
<!-- ✓ GOOD -->
<template>
<RuiRadio value="ok" />
</template>
<!-- ✗ BAD -->
<template>
<RuiRadio internal-value="ok" />
</template>
```

</eslint-code-block>

## :gear: Options

```json
{
"@rotki/no-deprecated-props": ["error"]
}
```

-

## :rocket: Version

This rule was introduced in `@rotki/eslint-plugin` v0.2.0

## :mag: Implementation

- [Rule source](https://github.com/rotki/eslint-plugin/blob/master/src/rules/no-deprecated-props.ts)
- [Test source](https://github.com/rotki/eslint-plugin/tree/master/tests/rules/no-deprecated-props.ts)
2 changes: 2 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pkg from '../package.json' assert { type: 'json' };
import noDeprecatedClasses from './rules/no-deprecated-classes';
import noDeprecatedComponents from './rules/no-deprecated-components';
import noDeprecatedProps from './rules/no-deprecated-props';
import type { ESLint } from 'eslint';

const plugin = {
Expand All @@ -11,6 +12,7 @@ const plugin = {
rules: {
'no-deprecated-classes': noDeprecatedClasses,
'no-deprecated-components': noDeprecatedComponents,
'no-deprecated-props': noDeprecatedProps,
},
} satisfies ESLint.Plugin;

Expand Down
87 changes: 87 additions & 0 deletions src/rules/no-deprecated-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { kebabCase, pascalCase } from 'scule';
import createDebug from 'debug';
import { createEslintRule, defineTemplateBodyVisitor } from '../utils';
import type { AST as VAST } from 'vue-eslint-parser';

const debug = createDebug('@rotki/eslint-plugin:no-deprecated-props');

export const RULE_NAME = 'no-deprecated-props';

export type MessageIds = 'replacedWith';

export type Options = [];

const replacements = {
RuiRadio: {
internalValue: 'value',
},
} as const;

function hasReplacement(tag: string): tag is (keyof typeof replacements) {
return Object.prototype.hasOwnProperty.call(replacements, tag);
}

function getPropName(node: VAST.VDirective | VAST.VAttribute): string | undefined {
if (node.directive) {
if (node.key.argument?.type !== 'VIdentifier')
return undefined;
return kebabCase(node.key.argument.rawName);
}
return kebabCase(node.key.rawName);
}

export default createEslintRule<Options, MessageIds>({
create(context) {
return defineTemplateBodyVisitor(context, {
VAttribute(node: VAST.VAttribute | VAST.VDirective) {
if (node.directive && (node.value?.type === 'VExpressionContainer' && (node.key.name.name !== 'bind' || !node.key.argument)))
return;

const tag = pascalCase(node.parent.parent.rawName);

if (!hasReplacement(tag))
return;

debug(`${tag} has replacement properties`);

const propName = getPropName(node);
const propNameNode = node.directive ? node.key.argument : node.key;

if (!propName || !propNameNode) {
debug('could not get prop name and/or node');
return;
}

Object.entries(replacements[tag]).forEach(([prop, replacement]) => {
if (kebabCase(prop) === propName) {
debug(`preparing a replacement for ${tag}:${propName} -> ${replacement}`);
context.report({
data: {
prop,
replacement,
},
fix(fixer) {
return fixer.replaceText(propNameNode, replacement);
},
messageId: 'replacedWith',
node: propNameNode,
});
}
});
},
});
},
defaultOptions: [],
meta: {
docs: {
description: '...',
},
fixable: 'code',
messages: {
replacedWith: `'{{ prop }}' has been replaced with '{{ replacement }}'`,
},
schema: [],
type: 'problem',
},
name: RULE_NAME,
});
11 changes: 11 additions & 0 deletions src/utils/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AssertionError extends Error {
constructor(msg: string) {
super(msg);
this.name = 'AssertionError';
}
}

export function assert(condition: any, msg?: string): asserts condition {
if (!condition)
throw new AssertionError(msg ?? 'AssertionError');
}
2 changes: 2 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from './node';
export * from './config';

export * from './array';

export * from './assertions';
66 changes: 66 additions & 0 deletions tests/rules/no-deprecated-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { RuleTester } from 'eslint';
import rule from '../../src/rules/no-deprecated-props';

const vueParser = require.resolve('vue-eslint-parser');

const tester = new RuleTester({
parser: vueParser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
});

tester.run('no-deprecated-props', rule as never, {
valid: [
{
filename: 'test.vue',
code: `
<script lang="ts">
const val = ref('')
</script>
<template>
<div>
<RuiRadio :value="val"/>
<RuiRadio value="ok"/>
</div>
</template>
`,
},
],
invalid: [
{
filename: 'test.vue',
code: `
<script lang="ts">
const val = ref('')
</script>
<template>
<div>
<RuiRadio :internal-value="val"/>
<RuiRadio internal-value="ok"/>
</div>
</template>
`.trim(),
output: `
<script lang="ts">
const val = ref('')
</script>
<template>
<div>
<RuiRadio :value="val"/>
<RuiRadio value="ok"/>
</div>
</template>
`.trim(),
errors: [
{
messageId: 'replacedWith',
},
{
messageId: 'replacedWith',
},
],
},
],
});

0 comments on commit 0a498ba

Please sign in to comment.