mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 06:53:03 +00:00
Enforce plugin structure and generate documentation
This commit is contained in:
@@ -6,11 +6,17 @@
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
import themedComponentUsages from './themed-component-usages';
|
||||
import {
|
||||
bundle,
|
||||
RuleExports,
|
||||
} from '../../util/structure';
|
||||
import * as themedComponentUsages from './themed-component-usages';
|
||||
|
||||
const index = [
|
||||
themedComponentUsages,
|
||||
] as unknown as RuleExports[];
|
||||
|
||||
export = {
|
||||
rules: {
|
||||
'themed-component-usages': themedComponentUsages,
|
||||
},
|
||||
parser: require('@angular-eslint/template-parser'),
|
||||
...bundle('dspace-angular-html', 'HTML', index),
|
||||
};
|
||||
|
@@ -5,20 +5,39 @@
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
import { fixture } from '../../../test/fixture';
|
||||
import { DSpaceESLintRuleInfo } from '../../util/structure';
|
||||
import {
|
||||
DISALLOWED_THEME_SELECTORS,
|
||||
fixSelectors,
|
||||
} from '../../util/theme-support';
|
||||
|
||||
export default {
|
||||
export enum Message {
|
||||
WRONG_SELECTOR = 'mustUseThemedWrapperSelector',
|
||||
}
|
||||
|
||||
export const info = {
|
||||
name: 'themed-component-usages',
|
||||
meta: {
|
||||
docs: {
|
||||
description: `Themeable components should be used via the selector of their \`ThemedComponent\` wrapper class
|
||||
|
||||
This ensures that custom themes can correctly override _all_ instances of this component.
|
||||
The only exception to this rule are unit tests, where we may want to use the base component in order to keep the test setup simple.
|
||||
`,
|
||||
},
|
||||
type: 'problem',
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
messages: {
|
||||
mustUseThemedWrapperSelector: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
|
||||
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
|
||||
},
|
||||
},
|
||||
defaultOptions: [],
|
||||
} as DSpaceESLintRuleInfo;
|
||||
|
||||
export const rule = {
|
||||
...info,
|
||||
create(context: any) {
|
||||
if (context.getFilename().includes('.spec.ts')) {
|
||||
// skip inline templates in unit tests
|
||||
@@ -28,7 +47,7 @@ export default {
|
||||
return {
|
||||
[`Element$1[name = /^${DISALLOWED_THEME_SELECTORS}/]`](node: any) {
|
||||
context.report({
|
||||
messageId: 'mustUseThemedWrapperSelector',
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
node,
|
||||
fix(fixer: any) {
|
||||
const oldSelector = node.name;
|
||||
@@ -59,3 +78,95 @@ export default {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const tests = {
|
||||
plugin: info.name,
|
||||
valid: [
|
||||
{
|
||||
code: `
|
||||
<ds-test-themeable/>
|
||||
<ds-test-themeable></ds-test-themeable>
|
||||
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: `
|
||||
@Component({
|
||||
template: '<ds-test-themeable></ds-test-themeable>'
|
||||
})
|
||||
class Test {
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
filename: fixture('src/test.spec.ts'),
|
||||
code: `
|
||||
@Component({
|
||||
template: '<ds-test-themeable></ds-test-themeable>'
|
||||
})
|
||||
class Test {
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
filename: fixture('src/test.spec.ts'),
|
||||
code: `
|
||||
@Component({
|
||||
template: '<ds-base-test-themeable></ds-base-test-themeable>'
|
||||
})
|
||||
class Test {
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
<ds-themed-test-themeable/>
|
||||
<ds-themed-test-themeable></ds-themed-test-themeable>
|
||||
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
],
|
||||
output: `
|
||||
<ds-test-themeable/>
|
||||
<ds-test-themeable></ds-test-themeable>
|
||||
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: `
|
||||
<ds-base-test-themeable/>
|
||||
<ds-base-test-themeable></ds-base-test-themeable>
|
||||
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
||||
`,
|
||||
errors: [
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
{
|
||||
messageId: Message.WRONG_SELECTOR,
|
||||
},
|
||||
],
|
||||
output: `
|
||||
<ds-test-themeable/>
|
||||
<ds-test-themeable></ds-test-themeable>
|
||||
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||
`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default rule;
|
||||
|
@@ -1,9 +1,15 @@
|
||||
import themedComponentSelectors from './themed-component-selectors';
|
||||
import themedComponentUsages from './themed-component-usages';
|
||||
import {
|
||||
bundle,
|
||||
RuleExports,
|
||||
} from '../../util/structure';
|
||||
import * as themedComponentUsages from './themed-component-usages';
|
||||
import * as themedComponentSelectors from './themed-component-selectors';
|
||||
|
||||
const index = [
|
||||
themedComponentUsages,
|
||||
themedComponentSelectors,
|
||||
] as unknown as RuleExports[];
|
||||
|
||||
export = {
|
||||
rules: {
|
||||
'themed-component-selectors': themedComponentSelectors,
|
||||
'themed-component-usages': themedComponentUsages,
|
||||
},
|
||||
...bundle('dspace-angular-ts', 'TypeScript', index),
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
68
lint/src/util/structure.ts
Normal file
68
lint/src/util/structure.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
import { TSESLint } from '@typescript-eslint/utils';
|
||||
import { RuleTester } from 'eslint';
|
||||
import { EnumType } from 'typescript';
|
||||
|
||||
export type Meta = TSESLint.RuleMetaData<string>;
|
||||
export type Valid = RuleTester.ValidTestCase | TSESLint.ValidTestCase<unknown[]>;
|
||||
export type Invalid = RuleTester.InvalidTestCase | TSESLint.InvalidTestCase<string, unknown[]>;
|
||||
|
||||
|
||||
export interface DSpaceESLintRuleInfo {
|
||||
name: string;
|
||||
meta: Meta,
|
||||
defaultOptions: any[],
|
||||
}
|
||||
|
||||
export interface DSpaceESLintTestInfo {
|
||||
rule: string;
|
||||
valid: Valid[];
|
||||
invalid: Invalid[];
|
||||
}
|
||||
|
||||
export interface DSpaceESLintPluginInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
rules: DSpaceESLintRuleInfo;
|
||||
tests: DSpaceESLintTestInfo;
|
||||
}
|
||||
|
||||
export interface DSpaceESLintInfo {
|
||||
html: DSpaceESLintPluginInfo;
|
||||
ts: DSpaceESLintPluginInfo;
|
||||
}
|
||||
|
||||
export interface RuleExports {
|
||||
Message: EnumType,
|
||||
info: DSpaceESLintRuleInfo,
|
||||
rule: any,
|
||||
tests: any,
|
||||
default: any,
|
||||
}
|
||||
|
||||
export function bundle(
|
||||
name: string,
|
||||
language: string,
|
||||
index: RuleExports[],
|
||||
): {
|
||||
name: string,
|
||||
language: string,
|
||||
rules: Record<string, any>,
|
||||
index: RuleExports[],
|
||||
} {
|
||||
return index.reduce((o: any, i: any) => {
|
||||
o.rules[i.info.name] = i.rule;
|
||||
return o;
|
||||
}, {
|
||||
name,
|
||||
language,
|
||||
rules: {},
|
||||
index,
|
||||
});
|
||||
}
|
5
lint/src/util/templates/index.ejs
Normal file
5
lint/src/util/templates/index.ejs
Normal file
@@ -0,0 +1,5 @@
|
||||
[DSpace ESLint plugins](../../README.md) > <%= plugin.language %> rules
|
||||
|
||||
<% rules.forEach(rule => { %>
|
||||
- [`<%= plugin.name %>/<%= rule.name %>`](./rules/<%= rule.name %>.md)<% if (rule.meta?.docs?.description) {%>: <%= rule.meta.docs.description.split('\n')[0] %><% }%>
|
||||
<% }) %>
|
36
lint/src/util/templates/rule.ejs
Normal file
36
lint/src/util/templates/rule.ejs
Normal file
@@ -0,0 +1,36 @@
|
||||
[DSpace ESLint plugins](../../../README.md) > [<%= plugin.language %> rules](../index.md) > `<%= plugin.name %>/<%= rule.name %>`
|
||||
_______
|
||||
|
||||
<%- rule.meta.docs?.description %>
|
||||
|
||||
_______
|
||||
|
||||
[Source code](../../../src/rules/<%- plugin.name.replace('dspace-angular-', '') %>/<%- rule.name %>.ts)
|
||||
|
||||
### Examples
|
||||
|
||||
<% if (tests.valid) {%>
|
||||
#### Valid code
|
||||
<% tests.valid.forEach(test => { %>
|
||||
<% if (test.filename) { %>
|
||||
Filename: `<%- test.filename %>`
|
||||
<% } %>
|
||||
```
|
||||
<%- test.code.trim() %>
|
||||
```
|
||||
<% }) %>
|
||||
<% } %>
|
||||
|
||||
<% if (tests.invalid) {%>
|
||||
#### Invalid code
|
||||
<% tests.invalid.forEach(test => { %>
|
||||
|
||||
<% if (test.filename) { %>
|
||||
Filename: `<%- test.filename %>`
|
||||
<% } %>
|
||||
```
|
||||
<%- test.code.trim() %>
|
||||
```
|
||||
|
||||
<% }) %>
|
||||
<% } %>
|
Reference in New Issue
Block a user