diff --git a/docs/lint/html/index.md b/docs/lint/html/index.md new file mode 100644 index 0000000000..15d693843c --- /dev/null +++ b/docs/lint/html/index.md @@ -0,0 +1,4 @@ +[DSpace ESLint plugins](../../../lint/README.md) > HTML rules +_______ + +- [`dspace-angular-html/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via the selector of their `ThemedComponent` wrapper class diff --git a/docs/lint/html/rules/themed-component-usages.md b/docs/lint/html/rules/themed-component-usages.md new file mode 100644 index 0000000000..a04fe1c770 --- /dev/null +++ b/docs/lint/html/rules/themed-component-usages.md @@ -0,0 +1,110 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [HTML rules](../index.md) > `dspace-angular-html/themed-component-usages` +_______ + +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. + + +_______ + +[Source code](../../../../lint/src/rules/html/themed-component-usages.ts) + +### Examples + + +#### Valid code + +##### use no-prefix selectors in HTML templates + +```html + + + +``` + +##### use no-prefix selectors in TypeScript templates + +```html +@Component({ + template: '' +}) +class Test { +} +``` + +##### use no-prefix selectors in TypeScript test templates + +Filename: `lint/test/fixture/src/test.spec.ts` + +```html +@Component({ + template: '' +}) +class Test { +} +``` + +##### base selectors are also allowed in TypeScript test templates + +Filename: `lint/test/fixture/src/test.spec.ts` + +```html +@Component({ + template: '' +}) +class Test { +} +``` + + + + +#### Invalid code & automatic fixes + +##### themed override selectors are not allowed in HTML templates + +```html + + + +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper's selector +Themeable components should be used via their ThemedComponent wrapper's selector +Themeable components should be used via their ThemedComponent wrapper's selector +``` + +Result of `yarn lint --fix`: +```html + + + +``` + + +##### base selectors are not allowed in HTML templates + +```html + + + +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper's selector +Themeable components should be used via their ThemedComponent wrapper's selector +Themeable components should be used via their ThemedComponent wrapper's selector +``` + +Result of `yarn lint --fix`: +```html + + + +``` + + + diff --git a/docs/lint/ts/index.md b/docs/lint/ts/index.md new file mode 100644 index 0000000000..ed060c946e --- /dev/null +++ b/docs/lint/ts/index.md @@ -0,0 +1,6 @@ +[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules +_______ + +- [`dspace-angular-ts/themed-component-classes`](./rules/themed-component-classes.md): Formatting rules for themeable component classes +- [`dspace-angular-ts/themed-component-selectors`](./rules/themed-component-selectors.md): Themeable component selectors should follow the DSpace convention +- [`dspace-angular-ts/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via their `ThemedComponent` wrapper class diff --git a/docs/lint/ts/rules/themed-component-classes.md b/docs/lint/ts/rules/themed-component-classes.md new file mode 100644 index 0000000000..571e3f170b --- /dev/null +++ b/docs/lint/ts/rules/themed-component-classes.md @@ -0,0 +1,253 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-classes` +_______ + +Formatting rules for themeable component classes + +_______ + +[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts) + +### Examples + + +#### Valid code + +##### Regular non-themeable component + +```typescript +@Component({ + selector: 'ds-something', + standalone: true, +}) +class Something { +} +``` + +##### Base component + +```typescript +@Component({ + selector: 'ds-base-test-themable', + standalone: true, +}) +class TestThemeableTomponent { +} +``` + +##### Wrapper component + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [ + TestThemeableComponent, + ], +}) +class ThemedTestThemeableTomponent extends ThemedComponent { +} +``` + +##### Override component + +Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-themed-test-themable', + standalone: true, +}) +class Override extends BaseComponent { +} +``` + + + + +#### Invalid code & automatic fixes + +##### Base component must be standalone + +```typescript +@Component({ + selector: 'ds-base-test-themable', +}) +class TestThemeableComponent { +} +``` +Will produce the following error(s): +``` +Themeable components must be standalone +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-base-test-themable', + standalone: true, +}) +class TestThemeableComponent { +} +``` + + +##### Wrapper component must be standalone and import base component + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-test-themable', +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` +Will produce the following error(s): +``` +Themeable component wrapper classes must be standalone and import the base class +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [TestThemeableComponent], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` + + +##### Wrapper component must import base component (array present but empty) + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` +Will produce the following error(s): +``` +Themed component wrapper classes must only import the base class +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [TestThemeableComponent], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` + + +##### Wrapper component must import base component (array is wrong) + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +import { SomethingElse } from './somewhere-else'; + +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [ + SomethingElse, + ], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` +Will produce the following error(s): +``` +Themed component wrapper classes must only import the base class +``` + +Result of `yarn lint --fix`: +```typescript +import { SomethingElse } from './somewhere-else'; + +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [TestThemeableComponent], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` + + +##### Wrapper component must import base component (array is wrong) + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +import { Something, SomethingElse } from './somewhere-else'; + +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [ + SomethingElse, + ], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` +Will produce the following error(s): +``` +Themed component wrapper classes must only import the base class +``` + +Result of `yarn lint --fix`: +```typescript +import { Something, SomethingElse } from './somewhere-else'; + +@Component({ + selector: 'ds-test-themable', + standalone: true, + imports: [TestThemeableComponent], +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` + + +##### Override component must be standalone + +Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-themed-test-themable', +}) +class Override extends BaseComponent { +} +``` +Will produce the following error(s): +``` +Themeable components must be standalone +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-themed-test-themable', + standalone: true, +}) +class Override extends BaseComponent { +} +``` + + + diff --git a/docs/lint/ts/rules/themed-component-selectors.md b/docs/lint/ts/rules/themed-component-selectors.md new file mode 100644 index 0000000000..f4d0ea177c --- /dev/null +++ b/docs/lint/ts/rules/themed-component-selectors.md @@ -0,0 +1,156 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-selectors` +_______ + +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. + + +_______ + +[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts) + +### Examples + + +#### Valid code + +##### Regular non-themeable component selector + +```typescript +@Component({ + selector: 'ds-something', +}) +class Something { +} +``` + +##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base- + +```typescript +@Component({ + selector: 'ds-base-something', +}) +class Something { +} + +@Component({ + selector: 'ds-something', +}) +class ThemedSomething extends ThemedComponent { +} + +@Component({ + selector: 'ds-themed-something', +}) +class OverrideSomething extends Something { +} +``` + +##### Other themed component wrappers should not interfere + +```typescript +@Component({ + selector: 'ds-something', +}) +class Something { +} + +@Component({ + selector: 'ds-something-else', +}) +class ThemedSomethingElse extends ThemedComponent { +} +``` + + + + +#### Invalid code & automatic fixes + +##### Wrong selector for base component + +Filename: `lint/test/fixture/src/app/test/test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-something', +}) +class TestThemeableComponent { +} +``` +Will produce the following error(s): +``` +Unthemed version of themeable component should have a selector starting with 'ds-base-' +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-base-something', +}) +class TestThemeableComponent { +} +``` + + +##### Wrong selector for wrapper component + +Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-themed-something', +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` +Will produce the following error(s): +``` +Themed component wrapper of themeable component shouldn't have a selector starting with 'ds-themed-' +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-something', +}) +class ThemedTestThemeableComponent extends ThemedComponent { +} +``` + + +##### Wrong selector for theme override + +Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.ts` + +```typescript +@Component({ + selector: 'ds-something', +}) +class TestThememeableComponent extends BaseComponent { +} +``` +Will produce the following error(s): +``` +Theme override of themeable component should have a selector starting with 'ds-themed-' +``` + +Result of `yarn lint --fix`: +```typescript +@Component({ + selector: 'ds-themed-something', +}) +class TestThememeableComponent extends BaseComponent { +} +``` + + + diff --git a/docs/lint/ts/rules/themed-component-usages.md b/docs/lint/ts/rules/themed-component-usages.md new file mode 100644 index 0000000000..16ccb701c2 --- /dev/null +++ b/docs/lint/ts/rules/themed-component-usages.md @@ -0,0 +1,332 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-component-usages` +_______ + +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 + + +_______ + +[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts) + +### Examples + + +#### Valid code + +##### allow wrapper class usages + +```typescript +import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component'; + +const config = { + a: ThemedTestThemeableComponent, + b: ChipsComponent, +} +``` + +##### allow base class in class declaration + +```typescript +export class TestThemeableComponent { +} +``` + +##### allow inheriting from base class + +```typescript +import { TestThemeableComponent } from './app/test/test-themeable.component'; + +export class ThemedAdminSidebarComponent extends ThemedComponent { +} +``` + +##### allow base class in ViewChild + +```typescript +import { TestThemeableComponent } from './app/test/test-themeable.component'; + +export class Something { + @ViewChild(TestThemeableComponent) test: TestThemeableComponent; +} +``` + +##### allow wrapper selectors in test queries + +Filename: `lint/test/fixture/src/app/test/test.component.spec.ts` + +```typescript +By.css('ds-themeable'); +By.css('#test > ds-themeable > #nest'); +``` + +##### allow wrapper selectors in cypress queries + +Filename: `lint/test/fixture/src/app/test/test.component.cy.ts` + +```typescript +By.css('ds-themeable'); +By.css('#test > ds-themeable > #nest'); +``` + + + + +#### Invalid code & automatic fixes + +##### disallow direct usages of base class + +```typescript +import { TestThemeableComponent } from './app/test/test-themeable.component'; +import { TestComponent } from './app/test/test.component'; + +const config = { + a: TestThemeableComponent, + b: TestComponent, +} +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component'; +import { TestComponent } from './app/test/test.component'; + +const config = { + a: ThemedTestThemeableComponent, + b: TestComponent, +} +``` + + +##### disallow direct usages of base class, keep other imports + +```typescript +import { Something, TestThemeableComponent } from './app/test/test-themeable.component'; +import { TestComponent } from './app/test/test.component'; + +const config = { + a: TestThemeableComponent, + b: TestComponent, + c: Something, +} +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +import { Something } from './app/test/test-themeable.component'; +import { ThemedTestThemeableComponent } from './app/test/themed-test-themeable.component'; +import { TestComponent } from './app/test/test.component'; + +const config = { + a: ThemedTestThemeableComponent, + b: TestComponent, + c: Something, +} +``` + + +##### handle array replacements correctly + +```typescript +const DECLARATIONS = [ + Something, + TestThemeableComponent, + Something, + ThemedTestThemeableComponent, +]; +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +const DECLARATIONS = [ + Something, + Something, + ThemedTestThemeableComponent, +]; +``` + + +##### disallow override selector in test queries + +Filename: `lint/test/fixture/src/app/test/test.component.spec.ts` + +```typescript +By.css('ds-themed-themeable'); +By.css('#test > ds-themed-themeable > #nest'); +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +By.css('ds-themeable'); +By.css('#test > ds-themeable > #nest'); +``` + + +##### disallow base selector in test queries + +Filename: `lint/test/fixture/src/app/test/test.component.spec.ts` + +```typescript +By.css('ds-base-themeable'); +By.css('#test > ds-base-themeable > #nest'); +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +By.css('ds-themeable'); +By.css('#test > ds-themeable > #nest'); +``` + + +##### disallow override selector in cypress queries + +Filename: `lint/test/fixture/src/app/test/test.component.cy.ts` + +```typescript +cy.get('ds-themed-themeable'); +cy.get('#test > ds-themed-themeable > #nest'); +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +cy.get('ds-themeable'); +cy.get('#test > ds-themeable > #nest'); +``` + + +##### disallow base selector in cypress queries + +Filename: `lint/test/fixture/src/app/test/test.component.cy.ts` + +```typescript +cy.get('ds-base-themeable'); +cy.get('#test > ds-base-themeable > #nest'); +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +cy.get('ds-themeable'); +cy.get('#test > ds-themeable > #nest'); +``` + + +##### edge case: unable to find usage node through usage token, but import is still flagged and fixed + +Filename: `lint/test/fixture/src/themes/test/app/test/other-themeable.component.ts` + +```typescript +import { Component } from '@angular/core'; + +import { Context } from './app/core/shared/context.model'; +import { TestThemeableComponent } from '../../../../app/test/test-themeable.component'; + +@Component({ + standalone: true, + imports: [TestThemeableComponent], +}) +export class UsageComponent { +} +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +import { Component } from '@angular/core'; + +import { Context } from './app/core/shared/context.model'; +import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component'; + +@Component({ + standalone: true, + imports: [ThemedTestThemeableComponent], +}) +export class UsageComponent { +} +``` + + +##### edge case edge case: both are imported, only wrapper is retained + +Filename: `lint/test/fixture/src/themes/test/app/test/other-themeable.component.ts` + +```typescript +import { Component } from '@angular/core'; + +import { Context } from './app/core/shared/context.model'; +import { TestThemeableComponent } from '../../../../app/test/test-themeable.component'; +import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component'; + +@Component({ + standalone: true, + imports: [TestThemeableComponent, ThemedTestThemeableComponent], +}) +export class UsageComponent { +} +``` +Will produce the following error(s): +``` +Themeable components should be used via their ThemedComponent wrapper +Themeable components should be used via their ThemedComponent wrapper +``` + +Result of `yarn lint --fix`: +```typescript +import { Component } from '@angular/core'; + +import { Context } from './app/core/shared/context.model'; +import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-themeable.component'; + +@Component({ + standalone: true, + imports: [ThemedTestThemeableComponent], +}) +export class UsageComponent { +} +``` + + +