diff --git a/eslint-plugin-tsoa/README.md b/eslint-plugin-tsoa/README.md index 6c871b1..95cea0e 100644 --- a/eslint-plugin-tsoa/README.md +++ b/eslint-plugin-tsoa/README.md @@ -62,6 +62,7 @@ export default [ | [require-example-decorator](docs/rules/require-example-decorator.md) | require the `@Example` decorator on methods that return an array | ✅ | | | | [require-jsdoc-example-for-enum-property-type](docs/rules/require-jsdoc-example-for-enum-property-type.md) | require JSDoc's `@example` declaration for enum properties | ✅ | | 💭 | | [require-jsdoc-returns](docs/rules/require-jsdoc-returns.md) | require return statements to be documented | ✅ | | 💭 | +| [require-property-example-decorator](docs/rules/require-property-example-decorator.md) | require the `@Example` decorator on class properties | ✅ | | | | [require-security-metadata](docs/rules/require-security-metadata.md) | require all security definitions used by the `@Security` decorator to be present in the `TSOA` config | ✅ | | | | [require-tags-decorator](docs/rules/require-tags-decorator.md) | require `@Tags` decorator on controllers | ✅ | | | | [require-tags-metadata](docs/rules/require-tags-metadata.md) | require tags used by the `@Tags` decorator to be present in the `TSOA` config | ✅ | | | diff --git a/eslint-plugin-tsoa/docs/rules/require-property-example-decorator.md b/eslint-plugin-tsoa/docs/rules/require-property-example-decorator.md new file mode 100644 index 0000000..7ad1d63 --- /dev/null +++ b/eslint-plugin-tsoa/docs/rules/require-property-example-decorator.md @@ -0,0 +1,7 @@ +# require-property-example-decorator + +Require the `@Example` decorator on class properties. + +💼 This rule is enabled in the ✅ `recommended` config. + + diff --git a/eslint-plugin-tsoa/index.d.ts b/eslint-plugin-tsoa/index.d.ts index d8a82bb..9e32937 100644 --- a/eslint-plugin-tsoa/index.d.ts +++ b/eslint-plugin-tsoa/index.d.ts @@ -9,6 +9,7 @@ declare const plugin: TSESLint.FlatConfig.Plugin & { 'no-jsdoc-example-for-complex-property-type': TSESLint.AnyRuleModule; 'require-example-decorator': TSESLint.AnyRuleModule; 'require-jsdoc-returns': TSESLint.AnyRuleModule; + 'require-property-example-decorator': TSESLint.AnyRuleModule; 'valid-alternative-response': TSESLint.AnyRuleModule; 'valid-alternative-response-type': TSESLint.AnyRuleModule; 'valid-security-decorator': TSESLint.AnyRuleModule; diff --git a/eslint-plugin-tsoa/package.json b/eslint-plugin-tsoa/package.json index e0192bd..62681eb 100644 --- a/eslint-plugin-tsoa/package.json +++ b/eslint-plugin-tsoa/package.json @@ -1,6 +1,6 @@ { "name": "@arabasta/eslint-plugin-tsoa", - "version": "1.0.0", + "version": "1.1.0", "type": "commonjs", "description": "ESLint plugin for tsoa rules", "author": "Stoyan Kolev", diff --git a/eslint-plugin-tsoa/src/eslint-plugin-tsoa.ts b/eslint-plugin-tsoa/src/eslint-plugin-tsoa.ts index 5eb722f..513cc4a 100644 --- a/eslint-plugin-tsoa/src/eslint-plugin-tsoa.ts +++ b/eslint-plugin-tsoa/src/eslint-plugin-tsoa.ts @@ -10,11 +10,12 @@ import requireSecurityMetadataRule from './rules/require-security-metadata.js'; import requireJsdocReturnsRule from './rules/require-jsdoc-returns.js'; import requireTagsDecoratorRule from './rules/require-tags-decorator.js'; import noJsdocExampleForComplexPropertyTypeRule from './rules/no-jsdoc-example-for-complex-property-type.js'; +import requirePropertyExampleDecoratorRule from './rules/require-property-example-decorator.js'; const plugin = { meta: { name: '@arabasta/eslint-plugin-tsoa', - version: '1.0.0', + version: '1.1.0', }, configs: {}, rules: { @@ -22,6 +23,7 @@ const plugin = { noJsdocExampleForComplexPropertyTypeRule, 'require-example-decorator': requireExampleDecoratorRule, 'require-jsdoc-returns': requireJsdocReturnsRule, + 'require-property-example-decorator': requirePropertyExampleDecoratorRule, 'valid-alternative-response': validAlternativeResponseRule, 'valid-alternative-response-type': validAlternativeResponseTypeRule, 'valid-security-decorator': validSecurityDecoratorRule, @@ -40,6 +42,7 @@ const recommendedRules = { '@arabasta/tsoa/no-jsdoc-example-for-complex-property-type': 'error', '@arabasta/tsoa/require-example-decorator': 'error', '@arabasta/tsoa/require-jsdoc-returns': 'error', + '@arabasta/tsoa/require-property-example-decorator': 'error', '@arabasta/tsoa/valid-alternative-response': 'error', '@arabasta/tsoa/valid-alternative-response-type': 'error', '@arabasta/tsoa/valid-security-decorator': 'error', diff --git a/eslint-plugin-tsoa/src/rules/require-property-example-decorator.test.ts b/eslint-plugin-tsoa/src/rules/require-property-example-decorator.test.ts new file mode 100644 index 0000000..e2e7a0c --- /dev/null +++ b/eslint-plugin-tsoa/src/rules/require-property-example-decorator.test.ts @@ -0,0 +1,42 @@ +import requirePropertyExampleDecorator from './require-property-example-decorator'; +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { normalizeIndent } from '../test-utils'; + +const ruleTester = new RuleTester(); + +// Throws error if the tests in ruleTester.run() do not pass +ruleTester.run( + 'require-property-example-decorator', // rule name + requirePropertyExampleDecorator, // rule code + { + // checks + // 'valid' checks cases that should pass + valid: [ + { + name: 'when the property has an `@Example` decorator', + code: normalizeIndent` + class Pet { + @Example("Max") + name: string; + } + `, + }, + ], + // 'invalid' checks cases that should not pass + invalid: [ + { + name: "when the property doesn't have an `@Example` decorator", + code: normalizeIndent` + class Pet { + name: string; + } + `, + errors: [ + { + messageId: 'missingExampleDecorator', + }, + ], + }, + ], + } +); diff --git a/eslint-plugin-tsoa/src/rules/require-property-example-decorator.ts b/eslint-plugin-tsoa/src/rules/require-property-example-decorator.ts new file mode 100644 index 0000000..d5412bd --- /dev/null +++ b/eslint-plugin-tsoa/src/rules/require-property-example-decorator.ts @@ -0,0 +1,30 @@ +import { createRule, hasDecoratorWithName } from '../utils.js'; + +export default createRule({ + name: 'require-property-example-decorator', + meta: { + messages: { + missingExampleDecorator: + "The '@Example' decorator is required on class properties", + }, + type: 'problem', + docs: { + description: 'require the `@Example` decorator on class properties', + recommended: true, + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + PropertyDefinition(node) { + if (!hasDecoratorWithName({ node, decoratorName: 'Example' })) { + context.report({ + node, + messageId: 'missingExampleDecorator', + }); + } + }, + }; + }, +}); diff --git a/eslint-plugin-tsoa/src/utils.ts b/eslint-plugin-tsoa/src/utils.ts index b532484..b06f4ea 100644 --- a/eslint-plugin-tsoa/src/utils.ts +++ b/eslint-plugin-tsoa/src/utils.ts @@ -86,7 +86,10 @@ export function hasDecoratorWithName({ node, decoratorName, }: { - node: TSESTree.MethodDefinition | TSESTree.ClassDeclaration; + node: + | TSESTree.MethodDefinition + | TSESTree.ClassDeclaration + | TSESTree.PropertyDefinition; decoratorName: string; }) { if (!Array.isArray(node?.decorators)) {