Enforce plugin structure and generate documentation

This commit is contained in:
Yury Bondarenko
2024-03-14 18:14:30 +01:00
parent e83a0cd741
commit b0758c23e5
19 changed files with 833 additions and 446 deletions

View File

@@ -6,8 +6,9 @@
* http://www.dspace.org/license/
*/
import { ESLintUtils } from '@typescript-eslint/utils';
import { fixture } from '../../../test/fixture';
import { findUsages } from '../../util/misc';
import { DSpaceESLintRuleInfo } from '../../util/structure';
import {
allThemeableComponents,
DISALLOWED_THEME_SELECTORS,
@@ -17,17 +18,40 @@ import {
isAllowedUnthemedUsage,
} from '../../util/theme-support';
export default ESLintUtils.RuleCreator.withoutDocs({
export enum Message {
WRONG_CLASS = 'mustUseThemedWrapperClass',
WRONG_IMPORT = 'mustImportThemedWrapper',
WRONG_SELECTOR = 'mustUseThemedWrapperSelector',
}
export const info = {
name: 'themed-component-usages',
meta: {
docs: {
description: `Themeable components should be used via their \`ThemedComponent\` wrapper class
This ensures that custom themes can correctly override _all_ instances of this component.
There are a few exceptions where the base class can still be used:
- Class declaration expressions (otherwise we can't declare, extend or override the class in the first place)
- Angular modules (except for routing modules)
- Angular \`@ViewChild\` decorators
- Type annotations
`,
},
type: 'problem',
schema: [],
fixable: 'code',
messages: {
mustUseThemedWrapper: 'Themeable components should be used via their ThemedComponent wrapper',
mustImportThemedWrapper: 'Themeable components should be used via their ThemedComponent wrapper',
[Message.WRONG_CLASS]: 'Themeable components should be used via their ThemedComponent wrapper',
[Message.WRONG_IMPORT]: 'Themeable components should be used via their ThemedComponent wrapper',
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper',
},
},
defaultOptions: [],
} as DSpaceESLintRuleInfo;
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: any, options: any): any {
function handleUnthemedUsagesInTypescript(node: any) {
if (isAllowedUnthemedUsage(node)) {
@@ -42,7 +66,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
}
context.report({
messageId: 'mustUseThemedWrapper',
messageId: Message.WRONG_CLASS,
node: node,
fix(fixer: any) {
return fixer.replaceText(node, entry.wrapperClass);
@@ -53,7 +77,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
function handleThemedSelectorQueriesInTests(node: any) {
context.report({
node,
messageId: 'mustUseThemedWrapper',
messageId: Message.WRONG_SELECTOR,
fix(fixer: any){
const newSelector = fixSelectors(node.raw);
return fixer.replaceText(node, newSelector);
@@ -79,7 +103,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
}
context.report({
messageId: 'mustImportThemedWrapper',
messageId: Message.WRONG_IMPORT,
node: importedNode,
fix(fixer: any) {
const ops = [];
@@ -133,3 +157,175 @@ export default ESLintUtils.RuleCreator.withoutDocs({
},
});
export const tests = {
plugin: info.name,
valid: [
{
name: 'allow wrapper class usages',
code: `
import { ThemedTestThemeableComponent } from '../test/themed-test-themeable.component.ts';
const config = {
a: ThemedTestThemeableComponent,
b: ChipsComponent,
}
`,
},
{
name: 'allow base class in class declaration',
code: `
export class TestThemeableComponent {
}
`,
},
{
name: 'allow inheriting from base class',
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
},
{
name: 'allow base class in ViewChild',
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
export class Something {
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
}
`,
},
{
name: 'allow wrapper selectors in test queries',
filename: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-themeable');
By.Css('#test > ds-themeable > #nest');
`,
},
{
name: 'allow wrapper selectors in cypress queries',
filename: fixture('src/app/test/test.component.cy.ts'),
code: `
By.css('ds-themeable');
By.Css('#test > ds-themeable > #nest');
`,
},
],
invalid: [
{
name: 'disallow direct usages of base class',
code: `
import { TestThemeableComponent } from '../test/test-themeable.component.ts';
import { TestComponent } from '../test/test.component.ts';
const config = {
a: TestThemeableComponent,
b: TestComponent,
}
`,
errors: [
{
messageId: Message.WRONG_IMPORT,
},
{
messageId: Message.WRONG_CLASS,
},
],
output: `
import { ThemedTestThemeableComponent } from '../test/themed-test-themeable.component.ts';
import { TestComponent } from '../test/test.component.ts';
const config = {
a: ThemedTestThemeableComponent,
b: TestComponent,
}
`,
},
{
name: 'disallow override selector in test queries',
filename: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-themed-themeable');
By.css('#test > ds-themed-themeable > #nest');
`,
errors: [
{
messageId: Message.WRONG_SELECTOR,
},
{
messageId: Message.WRONG_SELECTOR,
},
],
output: `
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
`,
},
{
name: 'disallow base selector in test queries',
filename: fixture('src/app/test/test.component.spec.ts'),
code: `
By.css('ds-base-themeable');
By.css('#test > ds-base-themeable > #nest');
`,
errors: [
{
messageId: Message.WRONG_SELECTOR,
},
{
messageId: Message.WRONG_SELECTOR,
},
],
output: `
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
`,
},
{
name: 'disallow override selector in cypress queries',
filename: fixture('src/app/test/test.component.cy.ts'),
code: `
cy.get('ds-themed-themeable');
cy.get('#test > ds-themed-themeable > #nest');
`,
errors: [
{
messageId: Message.WRONG_SELECTOR,
},
{
messageId: Message.WRONG_SELECTOR,
},
],
output: `
cy.get('ds-themeable');
cy.get('#test > ds-themeable > #nest');
`,
},
{
name: 'disallow base selector in cypress queries',
filename: fixture('src/app/test/test.component.cy.ts'),
code: `
cy.get('ds-base-themeable');
cy.get('#test > ds-base-themeable > #nest');
`,
errors: [
{
messageId: Message.WRONG_SELECTOR,
},
{
messageId: Message.WRONG_SELECTOR,
},
],
output: `
cy.get('ds-themeable');
cy.get('#test > ds-themeable > #nest');
`,
},
],
};
export default rule;