From c9dd6aab0091623bd3db4ead8c335c044ed8a7a3 Mon Sep 17 00:00:00 2001 From: MaxKless <34165455+MaxKless@users.noreply.github.com> Date: Mon, 21 Feb 2022 17:53:53 +0100 Subject: [PATCH] ci: add require-formly-code-documentation rule (#1019) --- .eslintrc.json | 1 + eslint-rules/src/index.ts | 2 + .../require-formly-code-documentation.ts | 66 +++++++++++++++++++ .../require-formly-code-documentation.spec.ts | 14 ++++ .../checkout-review-tac-field.component.ts | 3 + .../shipping-radio-wrapper.component.ts | 8 +++ .../formly/disable-prefilled.extension.ts | 6 +- .../registration-address-field.component.ts | 6 ++ .../registration-heading-field.component.ts | 8 +++ .../registration-tac-field.component.ts | 3 + .../formly-testing-example.component.ts | 1 + ...ly-testing-fieldgroup-example.component.ts | 1 + .../dev/testing/formly-testing.module.ts | 1 + .../plain-text-field.component.ts | 5 ++ .../text-input-field.component.ts | 2 +- 15 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 eslint-rules/src/rules/require-formly-code-documentation.ts create mode 100644 eslint-rules/tests/require-formly-code-documentation.spec.ts diff --git a/.eslintrc.json b/.eslintrc.json index dd1aeeee88..1df437f019 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -639,6 +639,7 @@ "ish-custom-rules/use-camel-case-environment-properties": "error", "ish-custom-rules/use-component-change-detection": "warn", "ish-custom-rules/use-jest-extended-matchers-in-tests": "warn", + "ish-custom-rules/require-formly-code-documentation": "warn", "jest/no-commented-out-tests": "warn", "jest/no-disabled-tests": "warn", "jest/no-focused-tests": "warn", diff --git a/eslint-rules/src/index.ts b/eslint-rules/src/index.ts index 763521a66b..7a0baaaa74 100644 --- a/eslint-rules/src/index.ts +++ b/eslint-rules/src/index.ts @@ -13,6 +13,7 @@ import { noVarBeforeReturnRule } from './rules/no-var-before-return'; import { orderedImportsRule } from './rules/ordered-imports'; import { privateDestroyFieldRule } from './rules/private-destroy-field'; import { projectStructureRule } from './rules/project-structure'; +import { requireFormlyCodeDocumentationRule } from './rules/require-formly-code-documentation'; import { useAliasImportsRule } from './rules/use-alias-imports'; import { useAsyncSynchronizationInTestsRule } from './rules/use-async-synchronization-in-tests'; import { useCamelCaseEnvironmentPropertiesRule } from './rules/use-camel-case-environment-properties'; @@ -40,6 +41,7 @@ const rules = { 'project-structure': projectStructureRule, 'newline-before-root-members': newlineBeforeRootMembersRule, 'no-var-before-return': noVarBeforeReturnRule, + 'require-formly-code-documentation': requireFormlyCodeDocumentationRule, }; module.exports = { diff --git a/eslint-rules/src/rules/require-formly-code-documentation.ts b/eslint-rules/src/rules/require-formly-code-documentation.ts new file mode 100644 index 0000000000..41241605f1 --- /dev/null +++ b/eslint-rules/src/rules/require-formly-code-documentation.ts @@ -0,0 +1,66 @@ +import { AST_NODE_TYPES, TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + +import { getClosestAncestorByKind } from '../helpers'; + +export const requireFormlyCodeDocumentationRule: TSESLint.RuleModule = { + meta: { + messages: { + missingDocumentationError: `Missing documentation for {{ artifactName }}. \n Please provide documentation for all Formly types, wrappers and extensions.`, + }, + type: 'problem', + schema: [], + }, + create: context => { + function hasPrecedingComment(node: TSESTree.ClassDeclaration) { + return ( + context.getSourceCode().getCommentsBefore(node)?.length > 0 || + (node.decorators?.[0] && context.getSourceCode().getCommentsBefore(node.decorators[0])?.length > 0) + ); + } + if (!context.getFilename().includes('formly')) { + return {}; + } + return { + ClassDeclaration(node) { + if (isFormlyArtifactClass(node) && !hasPrecedingComment(node)) { + context.report({ + node, + messageId: 'missingDocumentationError', + data: { + artifactName: node.id.name, + }, + }); + } + }, + 'ExportNamedDeclaration Identifier'(node: TSESTree.Identifier) { + if ( + node.typeAnnotation?.typeAnnotation?.type === AST_NODE_TYPES.TSTypeReference && + node.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && + node.typeAnnotation.typeAnnotation.typeName.name === 'FormlyExtension' && + context + .getSourceCode() + .getCommentsBefore(getClosestAncestorByKind(context, AST_NODE_TYPES.ExportNamedDeclaration)).length === 0 + ) { + context.report({ + node, + messageId: 'missingDocumentationError', + data: { + artifactName: node.name, + }, + }); + } + }, + }; + }, +}; + +function isFormlyArtifactClass(node: TSESTree.ClassDeclaration): boolean { + return ( + ['FieldType', 'FieldWrapper'].some( + superClass => node.superClass?.type === AST_NODE_TYPES.Identifier && node.superClass?.name === superClass + ) || + node.implements?.some( + impl => impl.expression.type === AST_NODE_TYPES.Identifier && impl.expression.name === 'FormlyExtension' + ) + ); +} diff --git a/eslint-rules/tests/require-formly-code-documentation.spec.ts b/eslint-rules/tests/require-formly-code-documentation.spec.ts new file mode 100644 index 0000000000..8bb9fffbd2 --- /dev/null +++ b/eslint-rules/tests/require-formly-code-documentation.spec.ts @@ -0,0 +1,14 @@ +import { requireFormlyCodeDocumentationRule } from '../src/rules/require-formly-code-documentation'; + +import { RuleTestConfig } from './_execute-tests'; + +const config: RuleTestConfig = { + ruleName: 'require-formly-code-documentation', + rule: requireFormlyCodeDocumentationRule, + tests: { + valid: [], + invalid: [], + }, +}; + +export default config; diff --git a/src/app/pages/checkout-review/formly/checkout-review-tac-field/checkout-review-tac-field.component.ts b/src/app/pages/checkout-review/formly/checkout-review-tac-field/checkout-review-tac-field.component.ts index e60d33caf5..acd5508f9f 100644 --- a/src/app/pages/checkout-review/formly/checkout-review-tac-field/checkout-review-tac-field.component.ts +++ b/src/app/pages/checkout-review/formly/checkout-review-tac-field/checkout-review-tac-field.component.ts @@ -1,6 +1,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldType, FieldTypeConfig } from '@ngx-formly/core'; +/** + * Type that renders a terms and conditions field, specific for the checkout review form. + */ @Component({ selector: 'ish-checkout-review-tac-field', templateUrl: './checkout-review-tac-field.component.html', diff --git a/src/app/pages/checkout-shipping/formly/shipping-radio-wrapper/shipping-radio-wrapper.component.ts b/src/app/pages/checkout-shipping/formly/shipping-radio-wrapper/shipping-radio-wrapper.component.ts index 3d05a9c0a1..2966c8c0da 100644 --- a/src/app/pages/checkout-shipping/formly/shipping-radio-wrapper/shipping-radio-wrapper.component.ts +++ b/src/app/pages/checkout-shipping/formly/shipping-radio-wrapper/shipping-radio-wrapper.component.ts @@ -1,6 +1,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldWrapper } from '@ngx-formly/core'; +/** + * Wrapper that handles checkout specific formatting and display of radio buttons. + * + * @templateOptions **shippingMethod** that will have its description displayed. + * @templateOptions **id** that will be used in the label. + * @tempalteOptions **labelClass* that will be applied to the label. + * + */ @Component({ selector: 'ish-shipping-radio-wrapper', templateUrl: './shipping-radio-wrapper.component.html', diff --git a/src/app/pages/registration/formly/disable-prefilled.extension.ts b/src/app/pages/registration/formly/disable-prefilled.extension.ts index bfa7d24107..61c16135a7 100644 --- a/src/app/pages/registration/formly/disable-prefilled.extension.ts +++ b/src/app/pages/registration/formly/disable-prefilled.extension.ts @@ -1,11 +1,11 @@ import { FormlyExtension, FormlyFieldConfig } from '@ngx-formly/core'; +type FieldConfigWithDisabled = Omit & { options: { formState: { disabled: string[] } } }; + /** - * This extension disables all fields that have keys that match those + * Extension that disables all fields that have keys that match those * specified in the formstate.disabled property */ -type FieldConfigWithDisabled = Omit & { options: { formState: { disabled: string[] } } }; - export const disablePrefilledExtension: FormlyExtension = { onPopulate(field) { if (hasDisabled(field) && field.key) { diff --git a/src/app/pages/registration/formly/registration-address-field/registration-address-field.component.ts b/src/app/pages/registration/formly/registration-address-field/registration-address-field.component.ts index 55a2039141..7165ce6ceb 100644 --- a/src/app/pages/registration/formly/registration-address-field/registration-address-field.component.ts +++ b/src/app/pages/registration/formly/registration-address-field/registration-address-field.component.ts @@ -2,6 +2,12 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { FieldType } from '@ngx-formly/core'; +/** + * Type that will render an component + * and configure it to use the current form as its parent. + * + * @templateOption **businessCustomer** will be passed on to the component (see component documentation for infos). + */ @Component({ selector: 'ish-registration-address-field', templateUrl: './registration-address-field.component.html', diff --git a/src/app/pages/registration/formly/registration-heading-field/registration-heading-field.component.ts b/src/app/pages/registration/formly/registration-heading-field/registration-heading-field.component.ts index fa18e10c62..3c6743704e 100644 --- a/src/app/pages/registration/formly/registration-heading-field/registration-heading-field.component.ts +++ b/src/app/pages/registration/formly/registration-heading-field/registration-heading-field.component.ts @@ -3,6 +3,14 @@ import { FieldType } from '@ngx-formly/core'; const sizes = ['h1', 'h2']; +/** + * Type that displays a section heading. + * + * @templateOption **heading** the primary heading text. Will be translated. + * @templateOption **subheading** the secondary heading text. Wil be translated. + * @templateOption **headingSize** determines whether the heading should be rendered with an

or

tag. + * @templateOption **showRequiredInfo** determines whether a message explaining the required star should be shown. + */ @Component({ selector: 'ish-registration-heading-field', templateUrl: './registration-heading-field.component.html', diff --git a/src/app/pages/registration/formly/registration-tac-field/registration-tac-field.component.ts b/src/app/pages/registration/formly/registration-tac-field/registration-tac-field.component.ts index 067c91c03e..ef7e182cee 100644 --- a/src/app/pages/registration/formly/registration-tac-field/registration-tac-field.component.ts +++ b/src/app/pages/registration/formly/registration-tac-field/registration-tac-field.component.ts @@ -1,6 +1,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldType, FieldTypeConfig } from '@ngx-formly/core'; +/** + * Type that will render a terms and conditions field, specific for the registration form. + */ @Component({ selector: 'ish-registration-tac-field', templateUrl: './registration-tac-field.component.html', diff --git a/src/app/shared/formly/dev/testing/formly-testing-example/formly-testing-example.component.ts b/src/app/shared/formly/dev/testing/formly-testing-example/formly-testing-example.component.ts index cb00292be5..7ee2f2e79c 100644 --- a/src/app/shared/formly/dev/testing/formly-testing-example/formly-testing-example.component.ts +++ b/src/app/shared/formly/dev/testing/formly-testing-example/formly-testing-example.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldType } from '@ngx-formly/core'; +// eslint-disable-next-line ish-custom-rules/require-formly-code-documentation @Component({ selector: 'ish-formly-testing-example', templateUrl: './formly-testing-example.component.html', diff --git a/src/app/shared/formly/dev/testing/formly-testing-fieldgroup-example/formly-testing-fieldgroup-example.component.ts b/src/app/shared/formly/dev/testing/formly-testing-fieldgroup-example/formly-testing-fieldgroup-example.component.ts index 85998ea700..b452b21ebf 100644 --- a/src/app/shared/formly/dev/testing/formly-testing-fieldgroup-example/formly-testing-fieldgroup-example.component.ts +++ b/src/app/shared/formly/dev/testing/formly-testing-fieldgroup-example/formly-testing-fieldgroup-example.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldType } from '@ngx-formly/core'; +// eslint-disable-next-line ish-custom-rules/require-formly-code-documentation @Component({ selector: 'ish-formly-fieldgroup-example', templateUrl: './formly-testing-fieldgroup-example.component.html', diff --git a/src/app/shared/formly/dev/testing/formly-testing.module.ts b/src/app/shared/formly/dev/testing/formly-testing.module.ts index bd8120822c..659e9b67ef 100644 --- a/src/app/shared/formly/dev/testing/formly-testing.module.ts +++ b/src/app/shared/formly/dev/testing/formly-testing.module.ts @@ -5,6 +5,7 @@ import { FieldType, FieldWrapper, FormlyFieldConfig, FormlyForm, FormlyModule } import { FormlySelectModule } from '@ngx-formly/core/select'; /* eslint-disable ish-custom-rules/project-structure */ +/* eslint-disable ish-custom-rules/require-formly-code-documentation */ @Component({ template: 'CaptchaFieldComponent: {{ field.key }} {{ to | json }}' }) class CaptchaFieldComponent extends FieldType {} diff --git a/src/app/shared/formly/types/plain-text-field/plain-text-field.component.ts b/src/app/shared/formly/types/plain-text-field/plain-text-field.component.ts index 2611c4d022..81c8a9778e 100644 --- a/src/app/shared/formly/types/plain-text-field/plain-text-field.component.ts +++ b/src/app/shared/formly/types/plain-text-field/plain-text-field.component.ts @@ -1,6 +1,11 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FieldType } from '@ngx-formly/core'; +/** + * Type to simply display a text value with optional styling + * + * @templateOption **inputClass** a class that will be used to style the div around the text + */ @Component({ selector: 'ish-plain-text-field', templateUrl: './plain-text-field.component.html', diff --git a/src/app/shared/formly/types/text-input-field/text-input-field.component.ts b/src/app/shared/formly/types/text-input-field/text-input-field.component.ts index e226dd1dbd..cb97dda7fe 100644 --- a/src/app/shared/formly/types/text-input-field/text-input-field.component.ts +++ b/src/app/shared/formly/types/text-input-field/text-input-field.component.ts @@ -4,7 +4,7 @@ import { FieldType, FieldTypeConfig, FormlyFieldConfig } from '@ngx-formly/core' /** * Type for a basic input field * - * @templateOption type supports all text types; 'text' (default), 'email', 'password', 'tel' + * @templateOption **type** supports all text types; 'text' (default), 'email', 'password', 'tel' * * @defaultWrappers form-field-horizontal & validation */