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,27 +6,53 @@
* http://www.dspace.org/license/
*/
import { ESLintUtils } from '@typescript-eslint/utils';
import { fixture } from '../../../test/fixture';
import { getComponentSelectorNode } from '../../util/angular';
import { stringLiteral } from '../../util/misc';
import { DSpaceESLintRuleInfo } from '../../util/structure';
import {
inThemedComponentOverrideFile,
isThemeableComponent,
isThemedComponentWrapper,
} from '../../util/theme-support';
export default ESLintUtils.RuleCreator.withoutDocs({
export enum Message {
BASE = 'wrongSelectorUnthemedComponent',
WRAPPER = 'wrongSelectorThemedComponentWrapper',
THEMED = 'wrongSelectorThemedComponentOverride',
}
export const info = {
name: 'themed-component-selectors',
meta: {
docs: {
description: `Themeable component selectors should follow the DSpace convention
Each themeable component is comprised of a base component, a wrapper component and any number of themed components
- Base components should have a selector starting with \`ds-base-\`
- Themed components should have a selector starting with \`ds-themed-\`
- Wrapper components should have a selector starting with \`ds-\`, but not \`ds-base-\` or \`ds-themed-\`
- This is the regular DSpace selector prefix
- **When making a regular component themeable, its selector prefix should be changed to \`ds-base-\`, and the new wrapper's component should reuse the previous selector**
Unit tests are exempt from this rule, because they may redefine components using the same class name as other themeable components elsewhere in the source.
`,
},
type: 'problem',
schema: [],
fixable: 'code',
messages: {
wrongSelectorUnthemedComponent: 'Unthemed version of themeable components should have a selector starting with \'ds-base-\'',
wrongSelectorThemedComponentWrapper: 'Themed component wrapper of themeable components shouldn\'t have a selector starting with \'ds-themed-\'',
wrongSelectorThemedComponentOverride: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
[Message.BASE]: 'Unthemed version of themeable components should have a selector starting with \'ds-base-\'',
[Message.WRAPPER]: 'Themed component wrapper of themeable components shouldn\'t have a selector starting with \'ds-themed-\'',
[Message.THEMED]: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
},
},
defaultOptions: [],
} as DSpaceESLintRuleInfo;
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: any): any {
if (context.getFilename()?.endsWith('.spec.ts')) {
return {};
@@ -35,7 +61,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
function enforceWrapperSelector(selectorNode: any) {
if (selectorNode?.value.startsWith('ds-themed-')) {
context.report({
messageId: 'wrongSelectorThemedComponentWrapper',
messageId: Message.WRAPPER,
node: selectorNode,
fix(fixer: any) {
return fixer.replaceText(selectorNode, stringLiteral(selectorNode.value.replace('ds-themed-', 'ds-')));
@@ -47,7 +73,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
function enforceBaseSelector(selectorNode: any) {
if (!selectorNode?.value.startsWith('ds-base-')) {
context.report({
messageId: 'wrongSelectorUnthemedComponent',
messageId: Message.BASE,
node: selectorNode,
fix(fixer: any) {
return fixer.replaceText(selectorNode, stringLiteral(selectorNode.value.replace('ds-', 'ds-base-')));
@@ -59,7 +85,7 @@ export default ESLintUtils.RuleCreator.withoutDocs({
function enforceThemedSelector(selectorNode: any) {
if (!selectorNode?.value.startsWith('ds-themed-')) {
context.report({
messageId: 'wrongSelectorThemedComponentOverride',
messageId: Message.THEMED,
node: selectorNode,
fix(fixer: any) {
return fixer.replaceText(selectorNode, stringLiteral(selectorNode.value.replace('ds-', 'ds-themed-')));
@@ -91,3 +117,130 @@ export default ESLintUtils.RuleCreator.withoutDocs({
};
},
});
export const tests = {
plugin: info.name,
valid: [
{
name: 'Regular non-themeable component selector',
code: `
@Component({
selector: 'ds-something',
})
class Something {
}
`,
},
{
name: 'Themeable component selector should replace the original version, unthemed version should be changed to ds-base-',
code: `
@Component({
selector: 'ds-base-something',
})
class Something {
}
@Component({
selector: 'ds-something',
})
class ThemedSomething extends ThemedComponent<Something> {
}
@Component({
selector: 'ds-themed-something',
})
class OverrideSomething extends Something {
}
`,
},
{
name: 'Other themed component wrappers should not interfere',
code: `
@Component({
selector: 'ds-something',
})
class Something {
}
@Component({
selector: 'ds-something-else',
})
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
}
`,
},
],
invalid: [
{
name: 'Wrong selector for base component',
filename: fixture('src/app/test/test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-something',
})
class TestThemeableComponent {
}
`,
errors: [
{
messageId: Message.BASE,
},
],
output: `
@Component({
selector: 'ds-base-something',
})
class TestThemeableComponent {
}
`,
},
{
name: 'Wrong selector for wrapper component',
filename: fixture('src/app/test/themed-test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-themed-something',
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
errors: [
{
messageId: Message.WRAPPER,
},
],
output: `
@Component({
selector: 'ds-something',
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
},
{
name: 'Wrong selector for theme override',
filename: fixture('src/themes/test/app/test/test-themeable.component.ts'),
code: `
@Component({
selector: 'ds-something',
})
class TestThememeableComponent extends BaseComponent {
}
`,
errors: [
{
messageId: Message.THEMED,
},
],
output: `
@Component({
selector: 'ds-themed-something',
})
class TestThememeableComponent extends BaseComponent {
}
`,
},
],
};
export default rule;