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..b6fe3d8c98 --- /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 || + 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;