-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add no-deprecated-components rule
- Loading branch information
Showing
9 changed files
with
314 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,4 @@ hero: | |
- theme: alt | ||
text: Get started | ||
link: /started | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
title: '@rotki/no-deprecated-components' | ||
description: description | ||
since: v0.1.0 | ||
--- | ||
|
||
# @rotki/no-deprecated-components | ||
|
||
> description | ||
## :book: Rule Details | ||
|
||
This rule reports ???. | ||
|
||
<eslint-code-block> | ||
|
||
<!-- eslint-skip --> | ||
|
||
```vue | ||
<script> | ||
/* eslint @rotki/no-deprecated-components: "error" */ | ||
</script> | ||
<!-- ✓ GOOD --> | ||
<!-- ✗ BAD --> | ||
``` | ||
|
||
</eslint-code-block> | ||
|
||
## :gear: Options | ||
|
||
```json | ||
{ | ||
"@rotki/no-deprecated-components": ["error", { | ||
"legacy": true | ||
}] | ||
} | ||
``` | ||
|
||
- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import createDebug from 'debug'; | ||
import { pascalCase, snakeCase } from 'scule'; | ||
import { createEslintRule, defineTemplateBodyVisitor, getSourceCode } from '../utils'; | ||
import type { VElement } from 'vue-eslint-parser/ast'; | ||
|
||
const debug = createDebug('@rotki/eslint-plugin:no-deprecated-components'); | ||
|
||
export const RULE_NAME = 'no-deprecated-components'; | ||
|
||
export type MessageIds = 'removed' | 'replacedWith' | 'deprecated'; | ||
|
||
export type Options = [{ legacy: boolean }]; | ||
|
||
const replacements = { | ||
DataTable: true, | ||
Fragment: false, | ||
} as const; | ||
|
||
const skipInLegacy = [ | ||
'Fragment', | ||
]; | ||
|
||
function hasReplacement(tag: string): tag is (keyof typeof replacements) { | ||
return Object.prototype.hasOwnProperty.call(replacements, tag); | ||
} | ||
|
||
export default createEslintRule<Options, MessageIds>({ | ||
create(context, optionsWithDefault) { | ||
const options = optionsWithDefault[0] || {}; | ||
const legacy = options.legacy; | ||
return defineTemplateBodyVisitor(context, { | ||
VElement(element: VElement) { | ||
const tag = pascalCase(element.rawName); | ||
|
||
const sourceCode = getSourceCode(context); | ||
if (!('getTemplateBodyTokenStore' in sourceCode.parserServices)) | ||
throw new Error('cannot find getTemplateBodyTokenStore in parserServices'); | ||
|
||
if (!hasReplacement(tag)) | ||
return; | ||
|
||
if (legacy && skipInLegacy.includes(tag)) { | ||
debug(`${tag} is skipped in legacy mode`); | ||
return; | ||
} | ||
|
||
const replacement = replacements[tag]; | ||
|
||
if (!replacement) { | ||
debug(`${tag} has will be removed`); | ||
context.report({ | ||
data: { | ||
name: snakeCase(tag), | ||
}, | ||
fix(fixer) { | ||
return [ | ||
fixer.remove(element.startTag), | ||
...(element.endTag ? [fixer.remove(element.endTag)] : []), | ||
]; | ||
}, | ||
messageId: 'removed', | ||
node: element, | ||
}); | ||
} | ||
else { | ||
debug(`${tag} has been deprecated`); | ||
context.report({ | ||
data: { | ||
name: snakeCase(tag), | ||
}, | ||
messageId: 'deprecated', | ||
node: element, | ||
}); | ||
} | ||
}, | ||
}); | ||
}, | ||
defaultOptions: [{ legacy: false }], | ||
meta: { | ||
docs: { | ||
description: 'Removes deprecated classes that do not exist anymore', | ||
recommended: 'recommended', | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
deprecated: `'{{ name }}' has been deprecated`, | ||
removed: `'{{ name }}' has been removed`, | ||
replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`, | ||
}, | ||
schema: [ | ||
{ | ||
additionalProperties: false, | ||
properties: { | ||
legacy: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
type: 'object', | ||
}, | ||
], | ||
type: 'problem', | ||
}, | ||
name: RULE_NAME, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,120 @@ | ||
import type { AST, Rule } from 'eslint'; | ||
import type { Node } from 'vue-eslint-parser/ast/nodes'; | ||
import type { SourceCode } from './vue-parser-services'; | ||
import type { Rule } from 'eslint'; | ||
import type { RuleContext as TSESLintRuleContext } from '@typescript-eslint/utils/ts-eslint'; | ||
import type { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; | ||
|
||
export interface RuleCreateAndOptions<TOptions extends readonly unknown[], TMessageIds extends string> { | ||
create: (context: Readonly<RuleContext<TMessageIds, TOptions>>, optionsWithDefault: Readonly<TOptions>) => TSESLint.RuleListener; | ||
defaultOptions: Readonly<TOptions>; | ||
} | ||
|
||
export interface RuleWithMeta<TOptions extends readonly unknown[], TMessageIds extends string> extends RuleCreateAndOptions<TOptions, TMessageIds> { | ||
meta: TSESLint.RuleMetaData<TMessageIds>; | ||
} | ||
|
||
export interface RuleWithMetaAndName<TOptions extends readonly unknown[], TMessageIds extends string> extends RuleCreateAndOptions<TOptions, TMessageIds> { | ||
meta: ESLintUtils.NamedCreateRuleMeta<TMessageIds>; | ||
name: string; | ||
} | ||
|
||
interface RuleFix { | ||
range: Readonly<AST.Range>; | ||
text: string; | ||
} | ||
|
||
type NodeOrToken = TSESTree.Node | TSESTree.Token | Node; | ||
|
||
interface RuleFixer { | ||
insertTextAfter(nodeOrToken: NodeOrToken, text: string): RuleFix; | ||
insertTextAfterRange(range: Readonly<AST.Range>, text: string): RuleFix; | ||
insertTextBefore(nodeOrToken: NodeOrToken, text: string): RuleFix; | ||
insertTextBeforeRange(range: Readonly<AST.Range>, text: string): RuleFix; | ||
remove(nodeOrToken: NodeOrToken): RuleFix; | ||
removeRange(range: Readonly<AST.Range>): RuleFix; | ||
replaceText(nodeOrToken: NodeOrToken, text: string): RuleFix; | ||
replaceTextRange(range: Readonly<AST.Range>, text: string): RuleFix; | ||
} | ||
|
||
interface SuggestionReportDescriptor<TMessageIds extends string> extends Omit<ReportDescriptorBase<TMessageIds>, 'fix'> { | ||
readonly fix: ReportFixFunction; | ||
} | ||
|
||
type ReportFixFunction = (fixer: RuleFixer) => IterableIterator<RuleFix> | RuleFix | readonly RuleFix[] | null; | ||
|
||
type ReportSuggestionArray<TMessageIds extends string> = SuggestionReportDescriptor<TMessageIds>[]; | ||
|
||
type ReportDescriptorMessageData = Readonly<Record<string, unknown>>; | ||
|
||
interface ReportDescriptorBase<TMessageIds extends string> { | ||
/** | ||
* The parameters for the message string associated with `messageId`. | ||
*/ | ||
readonly data?: ReportDescriptorMessageData; | ||
/** | ||
* The fixer function. | ||
*/ | ||
readonly fix?: ReportFixFunction | null; | ||
/** | ||
* The messageId which is being reported. | ||
*/ | ||
readonly messageId: TMessageIds; | ||
} | ||
|
||
interface ReportDescriptorWithSuggestion<TMessageIds extends string> extends ReportDescriptorBase<TMessageIds> { | ||
/** | ||
* 6.7's Suggestions API | ||
*/ | ||
readonly suggest?: Readonly<ReportSuggestionArray<TMessageIds>> | null; | ||
} | ||
|
||
interface ReportDescriptorNodeOptionalLoc { | ||
/** | ||
* The Node or AST Token which the report is being attached to | ||
*/ | ||
readonly node: NodeOrToken; | ||
/** | ||
* An override of the location of the report | ||
*/ | ||
readonly loc?: Readonly<TSESTree.Position> | Readonly<TSESTree.SourceLocation>; | ||
} | ||
|
||
interface ReportDescriptorLocOnly { | ||
/** | ||
* An override of the location of the report | ||
*/ | ||
loc: Readonly<TSESTree.Position> | Readonly<TSESTree.SourceLocation>; | ||
} | ||
|
||
export type ReportDescriptor<TMessageIds extends string> = ReportDescriptorWithSuggestion<TMessageIds> & (ReportDescriptorLocOnly | ReportDescriptorNodeOptionalLoc); | ||
|
||
export interface RuleModule< | ||
T extends readonly unknown[], | ||
> extends Rule.RuleModule { | ||
defaultOptions: T; | ||
TMessageIds extends string, | ||
TOptions extends readonly unknown[] = [], | ||
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener, | ||
> { | ||
/** | ||
* Default options the rule will be run with | ||
*/ | ||
defaultOptions: TOptions; | ||
/** | ||
* Metadata about the rule | ||
*/ | ||
meta: TSESLint.RuleMetaData<TMessageIds>; | ||
/** | ||
* Function which returns an object with methods that ESLint calls to “visit” | ||
* nodes while traversing the abstract syntax tree. | ||
*/ | ||
create(context: Readonly<RuleContext<TMessageIds, TOptions>>): TRuleListener; | ||
} | ||
|
||
export interface PluginRuleModule<TOptions extends readonly unknown[] = []> extends Rule.RuleModule { | ||
defaultOptions: TOptions; | ||
} | ||
|
||
export interface RuleContext< | ||
TMessageIds extends string, | ||
TOptions extends readonly unknown[], | ||
> extends Omit<TSESLintRuleContext<TMessageIds, TOptions>, 'sourceCode'> { | ||
> extends Omit<TSESLint.RuleContext<TMessageIds, TOptions>, 'sourceCode' | 'report'> { | ||
sourceCode: Readonly<SourceCode>; | ||
report(descriptor: ReportDescriptor<TMessageIds>): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { RuleTester } from 'eslint'; | ||
import rule from '../../src/rules/no-deprecated-components'; | ||
|
||
const vueParser = require.resolve('vue-eslint-parser'); | ||
|
||
const tester = new RuleTester({ | ||
parser: vueParser, | ||
parserOptions: { | ||
ecmaVersion: 2020, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
tester.run('no-deprecated-components', rule as never, { | ||
valid: [ | ||
{ | ||
filename: 'test.vue', | ||
code: `<template><Fragment><div></div></Fragment></template>`, | ||
options: [ | ||
{ | ||
legacy: true, | ||
}, | ||
], | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
filename: 'test.vue', | ||
code: `<template><Fragment><div></div></Fragment></template>`, | ||
output: `<template><div></div></template>`, | ||
errors: [ | ||
{ messageId: 'removed' }, | ||
], | ||
}, | ||
{ | ||
filename: 'test.vue', | ||
code: `<template><DataTable><div></div></DataTable></template>`, | ||
errors: [ | ||
{ messageId: 'deprecated' }, | ||
], | ||
}, | ||
], | ||
}); |