From 4200357100762064cfa4fb4f22a68822d33e47ee Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 29 Aug 2024 16:29:59 +0200 Subject: [PATCH] 117616: Ported themed-wrapper-no-input-defaults rule --- .eslintrc.json | 3 +- docs/lint/ts/index.md | 1 + .../rules/themed-wrapper-no-input-defaults.md | 82 +++++++++ lint/src/rules/ts/index.ts | 2 + .../ts/themed-wrapper-no-input-defaults.ts | 157 ++++++++++++++++++ 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 docs/lint/ts/rules/themed-wrapper-no-input-defaults.md create mode 100644 lint/src/rules/ts/themed-wrapper-no-input-defaults.ts diff --git a/.eslintrc.json b/.eslintrc.json index 656722cfe9..6f258de5a8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -266,7 +266,8 @@ "dspace-angular-ts/alias-imports": "error", "dspace-angular-ts/themed-component-classes": "error", "dspace-angular-ts/themed-component-selectors": "error", - "dspace-angular-ts/themed-component-usages": "error" + "dspace-angular-ts/themed-component-usages": "error", + "dspace-angular-ts/themed-wrapper-no-input-defaults": "error" } }, { diff --git a/docs/lint/ts/index.md b/docs/lint/ts/index.md index 3411202be9..1ce57cde16 100644 --- a/docs/lint/ts/index.md +++ b/docs/lint/ts/index.md @@ -5,3 +5,4 @@ _______ - [`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 +- [`dspace-angular-ts/themed-wrapper-no-input-defaults`](./rules/themed-wrapper-no-input-defaults.md): ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164)) diff --git a/docs/lint/ts/rules/themed-wrapper-no-input-defaults.md b/docs/lint/ts/rules/themed-wrapper-no-input-defaults.md new file mode 100644 index 0000000000..045af8b3ad --- /dev/null +++ b/docs/lint/ts/rules/themed-wrapper-no-input-defaults.md @@ -0,0 +1,82 @@ +[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-wrapper-no-input-defaults` +_______ + +ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164)) + +_______ + +[Source code](../../../../lint/src/rules/ts/themed-wrapper-no-input-defaults.ts) + +### Examples + + +#### Valid code + +##### ThemedComponent wrapper defines an input without a default value + +```typescript +export class TTest extends ThemedComponent { + +@Input() +test; +} +``` + +##### Regular class defines an input with a default value + +```typescript +export class Test { + +@Input() +test = 'test'; +} +``` + + + + +#### Invalid code + +##### ThemedComponent wrapper defines an input with a default value + +```typescript +export class TTest extends ThemedComponent { + +@Input() +test1 = 'test'; + +@Input() +test2 = true; + +@Input() +test2: number = 123; + +@Input() +test3: number[] = [1,2,3]; +} +``` +Will produce the following error(s): +``` +ThemedComponent wrapper declares inputs with defaults +ThemedComponent wrapper declares inputs with defaults +ThemedComponent wrapper declares inputs with defaults +ThemedComponent wrapper declares inputs with defaults +``` + + +##### ThemedComponent wrapper defines an input with an undefined default value + +```typescript +export class TTest extends ThemedComponent { + +@Input() +test = undefined; +} +``` +Will produce the following error(s): +``` +ThemedComponent wrapper declares inputs with defaults +``` + + + diff --git a/lint/src/rules/ts/index.ts b/lint/src/rules/ts/index.ts index 0b42ee24c4..19fa38d6ac 100644 --- a/lint/src/rules/ts/index.ts +++ b/lint/src/rules/ts/index.ts @@ -14,12 +14,14 @@ import * as aliasImports from './alias-imports'; import * as themedComponentClasses from './themed-component-classes'; import * as themedComponentSelectors from './themed-component-selectors'; import * as themedComponentUsages from './themed-component-usages'; +import * as themedWrapperNoInputDefaults from './themed-wrapper-no-input-defaults'; const index = [ aliasImports, themedComponentClasses, themedComponentSelectors, themedComponentUsages, + themedWrapperNoInputDefaults, ] as unknown as RuleExports[]; export = { diff --git a/lint/src/rules/ts/themed-wrapper-no-input-defaults.ts b/lint/src/rules/ts/themed-wrapper-no-input-defaults.ts new file mode 100644 index 0000000000..0af9b8b2e2 --- /dev/null +++ b/lint/src/rules/ts/themed-wrapper-no-input-defaults.ts @@ -0,0 +1,157 @@ +import { + ESLintUtils, + TSESTree, +} from '@typescript-eslint/utils'; +import { RuleContext } from '@typescript-eslint/utils/ts-eslint'; + +import { + DSpaceESLintRuleInfo, + NamedTests, +} from '../../util/structure'; +import { isThemedComponentWrapper } from '../../util/theme-support'; + +export enum Message { + WRAPPER_HAS_INPUT_DEFAULTS = 'wrapperHasInputDefaults', +} + +export const info: DSpaceESLintRuleInfo = { + name: 'themed-wrapper-no-input-defaults', + meta: { + docs: { + description: 'ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))', + }, + messages: { + [Message.WRAPPER_HAS_INPUT_DEFAULTS]: 'ThemedComponent wrapper declares inputs with defaults', + }, + type: 'problem', + schema: [], + }, + defaultOptions: [], +}; + +export const rule = ESLintUtils.RuleCreator.withoutDocs({ + ...info, + create(context: RuleContext, options: any) { + return { + 'ClassBody > PropertyDefinition > Decorator > CallExpression[callee.name=\'Input\']': (node: TSESTree.CallExpression) => { + const classDeclaration = (node?.parent?.parent?.parent as TSESTree.Decorator); // todo: clean this up + if (!isThemedComponentWrapper(classDeclaration)) { + return; + } + + const propertyDefinition: TSESTree.PropertyDefinition = (node.parent.parent as any); // todo: clean this up + + if (propertyDefinition.value !== null) { + context.report({ + messageId: Message.WRAPPER_HAS_INPUT_DEFAULTS, + node: propertyDefinition.value, + // fix(fixer) { + // // todo: don't strip type annotations! + // // todo: replace default with appropriate type annotation if not present! + // return fixer.removeRange([propertyDefinition.key.range[1], (propertyDefinition.value as any).range[1]]); + // } + }); + } + }, + }; + }, +}); + +export const tests: NamedTests = { + plugin: info.name, + valid: [ + { + name: 'ThemedComponent wrapper defines an input without a default value', + code: ` +export class TTest extends ThemedComponent { + +@Input() +test; +} + `, + }, + { + name: 'Regular class defines an input with a default value', + code: ` +export class Test { + +@Input() +test = 'test'; +} + `, + }, + ], + invalid: [ + { + name: 'ThemedComponent wrapper defines an input with a default value', + code: ` +export class TTest extends ThemedComponent { + +@Input() +test1 = 'test'; + +@Input() +test2 = true; + +@Input() +test2: number = 123; + +@Input() +test3: number[] = [1,2,3]; +} + `, + errors: [ + { + messageId: 'wrapperHasInputDefaults', + }, + { + messageId: 'wrapperHasInputDefaults', + }, + { + messageId: 'wrapperHasInputDefaults', + }, + { + messageId: 'wrapperHasInputDefaults', + }, + ], + // output: ` + // export class TTest extends ThemedComponent { + // + // @Input() + // test1: string; + // + // @Input() + // test2: boolean; + // + // @Input() + // test2: number; + // + // @Input() + // test3: number[]; + // } + // `, + }, + { + name: 'ThemedComponent wrapper defines an input with an undefined default value', + code: ` +export class TTest extends ThemedComponent { + +@Input() +test = undefined; +} + `, + errors: [ + { + messageId: 'wrapperHasInputDefaults', + }, + ], + // output: ` + // export class TTest extends ThemedComponent { + // + // @Input() + // test; + // } + // `, + }, + ], +};