@living-architecture/riviere-extract-conventions-published-language 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +69 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +69 -0
  6. package/dist/published-language/decorators.d.ts +83 -0
  7. package/dist/published-language/decorators.d.ts.map +1 -0
  8. package/dist/published-language/decorators.js +130 -0
  9. package/package.json +44 -0
  10. package/src/published-language/default-extraction.config.json +153 -0
  11. package/src/published-language/eslint-plugin/api-controller-requires-route-and-method.cjs +85 -0
  12. package/src/published-language/eslint-plugin/api-controller-requires-route-and-method.d.cts +6 -0
  13. package/src/published-language/eslint-plugin/event-handler-requires-subscribed-events.cjs +47 -0
  14. package/src/published-language/eslint-plugin/event-handler-requires-subscribed-events.d.cts +6 -0
  15. package/src/published-language/eslint-plugin/event-publisher-method-signature.cjs +179 -0
  16. package/src/published-language/eslint-plugin/event-publisher-method-signature.d.cts +6 -0
  17. package/src/published-language/eslint-plugin/event-requires-type-property.cjs +47 -0
  18. package/src/published-language/eslint-plugin/event-requires-type-property.d.cts +6 -0
  19. package/src/published-language/eslint-plugin/http-call-requires-http-client-container.cjs +68 -0
  20. package/src/published-language/eslint-plugin/http-call-requires-http-client-container.d.cts +5 -0
  21. package/src/published-language/eslint-plugin/http-call-requires-route.cjs +104 -0
  22. package/src/published-language/eslint-plugin/http-call-requires-route.d.cts +5 -0
  23. package/src/published-language/eslint-plugin/http-client-public-methods-require-http-call.cjs +80 -0
  24. package/src/published-language/eslint-plugin/http-client-public-methods-require-http-call.d.cts +5 -0
  25. package/src/published-language/eslint-plugin/http-client-requires-remote-name.cjs +77 -0
  26. package/src/published-language/eslint-plugin/http-client-requires-remote-name.d.cts +5 -0
  27. package/src/published-language/eslint-plugin/index.cjs +64 -0
  28. package/src/published-language/eslint-plugin/index.d.ts +57 -0
  29. package/src/published-language/eslint-plugin/interface-ast-predicates.cjs +83 -0
  30. package/src/published-language/eslint-plugin/no-fetch-outside-http-client.cjs +60 -0
  31. package/src/published-language/eslint-plugin/no-fetch-outside-http-client.d.cts +5 -0
  32. package/src/published-language/eslint-plugin/require-component-decorator.cjs +56 -0
  33. package/src/published-language/eslint-plugin/require-component-decorator.d.cts +4 -0
  34. package/src/published-language/eslint-plugin/rule-module-declarations.d.ts +59 -0
  35. package/src/published-language/eslint-plugin/ui-page-requires-route.cjs +47 -0
  36. package/src/published-language/eslint-plugin/ui-page-requires-route.d.cts +6 -0
@@ -0,0 +1,104 @@
1
+ function findHttpCallDecoratorExpression(decorators) {
2
+ for (const decorator of decorators) {
3
+ const expression = decorator.expression
4
+ if (expression.type === 'Identifier' && expression.name === 'HttpCall') {
5
+ return expression
6
+ }
7
+ if (expression.type !== 'CallExpression') {
8
+ continue
9
+ }
10
+ if (expression.callee.type !== 'Identifier' || expression.callee.name !== 'HttpCall') {
11
+ continue
12
+ }
13
+ return expression
14
+ }
15
+ return null
16
+ }
17
+
18
+ function getRouteViolation(decoratorExpression) {
19
+ if (decoratorExpression.type === 'Identifier') {
20
+ return 'missingRoute'
21
+ }
22
+ if (decoratorExpression.arguments.length === 0) {
23
+ return 'missingRoute'
24
+ }
25
+
26
+ const firstArgument = decoratorExpression.arguments[0]
27
+ if (firstArgument.type !== 'Literal' || typeof firstArgument.value !== 'string') {
28
+ return 'routeNotLiteral'
29
+ }
30
+
31
+ if (firstArgument.value.trim().length === 0) {
32
+ return 'emptyRoute'
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ function getMethodViolation(decoratorExpression) {
39
+ if (decoratorExpression.arguments.length < 2) {
40
+ return 'missingMethod'
41
+ }
42
+
43
+ const secondArgument = decoratorExpression.arguments[1]
44
+ if (secondArgument.type !== 'Literal' || typeof secondArgument.value !== 'string') {
45
+ return 'methodNotLiteral'
46
+ }
47
+
48
+ if (secondArgument.value.trim().length === 0) {
49
+ return 'emptyMethod'
50
+ }
51
+
52
+ return null
53
+ }
54
+
55
+ module.exports = {
56
+ meta: {
57
+ type: 'problem',
58
+ docs: { description: 'Require HttpCall decorator to include route and method' },
59
+ schema: [],
60
+ messages: {
61
+ missingRoute: "Method '{{methodName}}' uses @HttpCall but is missing route argument",
62
+ missingMethod: "Method '{{methodName}}' uses @HttpCall but is missing method argument",
63
+ routeNotLiteral: "Method '{{methodName}}' uses @HttpCall but route must be a string literal",
64
+ methodNotLiteral: "Method '{{methodName}}' uses @HttpCall but method must be a string literal",
65
+ emptyRoute: "Method '{{methodName}}' uses @HttpCall but route must be non-empty",
66
+ emptyMethod: "Method '{{methodName}}' uses @HttpCall but method must be non-empty",
67
+ },
68
+ },
69
+ create(context) {
70
+ return {
71
+ MethodDefinition(node) {
72
+ if (node.key.type !== 'Identifier' || !node.decorators) {
73
+ return
74
+ }
75
+
76
+ const decoratorExpression = findHttpCallDecoratorExpression(node.decorators)
77
+ if (!decoratorExpression) {
78
+ return
79
+ }
80
+
81
+ const violation = getRouteViolation(decoratorExpression)
82
+ if (!violation) {
83
+ const methodViolation = getMethodViolation(decoratorExpression)
84
+ if (!methodViolation) {
85
+ return
86
+ }
87
+
88
+ context.report({
89
+ node: node.key,
90
+ messageId: methodViolation,
91
+ data: { methodName: node.key.name },
92
+ })
93
+ return
94
+ }
95
+
96
+ context.report({
97
+ node: node.key,
98
+ messageId: violation,
99
+ data: { methodName: node.key.name },
100
+ })
101
+ },
102
+ }
103
+ },
104
+ }
@@ -0,0 +1,5 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ declare const rule: TSESLint.RuleModule<string, readonly unknown[]>
4
+
5
+ export = rule
@@ -0,0 +1,80 @@
1
+ function hasDecoratorNamed(decorators, decoratorName) {
2
+ /* v8 ignore next 3 -- typescript-eslint provides an array for decorators on class and method nodes; undefined is structurally unreachable in current parser output */
3
+ if (!decorators) {
4
+ return false
5
+ }
6
+
7
+ return decorators.some((decorator) => {
8
+ const expression = decorator.expression
9
+ if (expression.type === 'Identifier') {
10
+ return expression.name === decoratorName
11
+ }
12
+ return (
13
+ expression.type === 'CallExpression' &&
14
+ expression.callee.type === 'Identifier' &&
15
+ expression.callee.name === decoratorName
16
+ )
17
+ })
18
+ }
19
+
20
+ function isPublicInstanceMethod(member) {
21
+ if (member.type !== 'MethodDefinition') {
22
+ return false
23
+ }
24
+ if (member.kind === 'constructor' || member.kind === 'get' || member.kind === 'set') {
25
+ return false
26
+ }
27
+ if (member.static) {
28
+ return false
29
+ }
30
+ if (member.accessibility === 'private' || member.accessibility === 'protected') {
31
+ return false
32
+ }
33
+ return member.key.type === 'Identifier'
34
+ }
35
+
36
+ module.exports = {
37
+ meta: {
38
+ type: 'problem',
39
+ docs: {description: 'Require public methods in HttpClient classes to have HttpCall decorator',},
40
+ schema: [],
41
+ messages: {
42
+ missingHttpCall:
43
+ "Public method '{{methodName}}' in @HttpClient class '{{className}}' must have @HttpCall",
44
+ },
45
+ },
46
+ create(context) {
47
+ return {
48
+ ClassDeclaration(node) {
49
+ if (hasDecoratorNamed(node.decorators, 'Ignore')) {
50
+ return
51
+ }
52
+
53
+ if (!node.id || !hasDecoratorNamed(node.decorators, 'HttpClient')) {
54
+ return
55
+ }
56
+
57
+ for (const member of node.body.body) {
58
+ if (hasDecoratorNamed(member.decorators, 'Ignore')) {
59
+ continue
60
+ }
61
+ if (!isPublicInstanceMethod(member)) {
62
+ continue
63
+ }
64
+ if (hasDecoratorNamed(member.decorators, 'HttpCall')) {
65
+ continue
66
+ }
67
+
68
+ context.report({
69
+ node: member.key,
70
+ messageId: 'missingHttpCall',
71
+ data: {
72
+ className: node.id.name,
73
+ methodName: member.key.name,
74
+ },
75
+ })
76
+ }
77
+ },
78
+ }
79
+ },
80
+ }
@@ -0,0 +1,5 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ declare const rule: TSESLint.RuleModule<string, readonly unknown[]>
4
+
5
+ export = rule
@@ -0,0 +1,77 @@
1
+ function findHttpClientDecoratorExpression(decorators) {
2
+ for (const decorator of decorators) {
3
+ const expression = decorator.expression
4
+ if (expression.type === 'Identifier' && expression.name === 'HttpClient') {
5
+ return expression
6
+ }
7
+ if (expression.type !== 'CallExpression') {
8
+ continue
9
+ }
10
+ if (expression.callee.type !== 'Identifier' || expression.callee.name !== 'HttpClient') {
11
+ continue
12
+ }
13
+ return expression
14
+ }
15
+ return null
16
+ }
17
+
18
+ function getServiceNameViolation(callExpression) {
19
+ if (callExpression.type === 'Identifier') {
20
+ return 'missingServiceName'
21
+ }
22
+
23
+ if (callExpression.arguments.length === 0) {
24
+ return 'missingServiceName'
25
+ }
26
+
27
+ const firstArgument = callExpression.arguments[0]
28
+ if (firstArgument.type !== 'Literal' || typeof firstArgument.value !== 'string') {
29
+ return 'serviceNameNotLiteral'
30
+ }
31
+
32
+ if (firstArgument.value.trim().length === 0) {
33
+ return 'emptyServiceName'
34
+ }
35
+
36
+ return null
37
+ }
38
+
39
+ module.exports = {
40
+ meta: {
41
+ type: 'problem',
42
+ docs: { description: 'Require HttpClient decorator to include service name' },
43
+ schema: [],
44
+ messages: {
45
+ missingServiceName:
46
+ "Class '{{className}}' uses @HttpClient but is missing service name argument",
47
+ serviceNameNotLiteral:
48
+ "Class '{{className}}' uses @HttpClient but service name must be a string literal",
49
+ emptyServiceName: "Class '{{className}}' uses @HttpClient but service name must be non-empty",
50
+ },
51
+ },
52
+ create(context) {
53
+ return {
54
+ ClassDeclaration(node) {
55
+ if (!node.id || !node.decorators) {
56
+ return
57
+ }
58
+
59
+ const decoratorCall = findHttpClientDecoratorExpression(node.decorators)
60
+ if (!decoratorCall) {
61
+ return
62
+ }
63
+
64
+ const violation = getServiceNameViolation(decoratorCall)
65
+ if (!violation) {
66
+ return
67
+ }
68
+
69
+ context.report({
70
+ node: node.id,
71
+ messageId: violation,
72
+ data: { className: node.id.name },
73
+ })
74
+ },
75
+ }
76
+ },
77
+ }
@@ -0,0 +1,5 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ declare const rule: TSESLint.RuleModule<string, readonly unknown[]>
4
+
5
+ export = rule
@@ -0,0 +1,64 @@
1
+ const requireComponentDecorator = require('./require-component-decorator.cjs')
2
+ const apiControllerRequiresRouteAndMethod = require('./api-controller-requires-route-and-method.cjs')
3
+ const eventRequiresTypeProperty = require('./event-requires-type-property.cjs')
4
+ const eventHandlerRequiresSubscribedEvents = require('./event-handler-requires-subscribed-events.cjs')
5
+ const uiPageRequiresRoute = require('./ui-page-requires-route.cjs')
6
+ const eventPublisherMethodSignature = require('./event-publisher-method-signature.cjs')
7
+ const httpClientRequiresRemoteName = require('./http-client-requires-remote-name.cjs')
8
+ const httpCallRequiresRoute = require('./http-call-requires-route.cjs')
9
+ const httpCallRequiresHttpClientContainer = require('./http-call-requires-http-client-container.cjs')
10
+ const httpClientPublicMethodsRequireHttpCall = require('./http-client-public-methods-require-http-call.cjs')
11
+ const noFetchOutsideHttpClient = require('./no-fetch-outside-http-client.cjs')
12
+
13
+ const restrictedHttpClientImports = [
14
+ 'axios',
15
+ 'got',
16
+ 'node-fetch',
17
+ 'undici',
18
+ 'superagent',
19
+ 'node:http',
20
+ 'node:https',
21
+ ]
22
+
23
+ function toRestrictedImportPath(packageName) {
24
+ return {
25
+ name: packageName,
26
+ message: `Do not import '${packageName}' directly. Use @HttpClient/@HttpCall conventions for HTTP boundary code.`,
27
+ }
28
+ }
29
+
30
+ function toRestrictedImportPattern(packageName) {
31
+ return {
32
+ group: [`${packageName}/*`],
33
+ message: `Do not import subpaths from '${packageName}'. Use @HttpClient/@HttpCall conventions for HTTP boundary code.`,
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ rules: {
39
+ 'require-component-decorator': requireComponentDecorator,
40
+ 'api-controller-requires-route-and-method': apiControllerRequiresRouteAndMethod,
41
+ 'event-requires-type-property': eventRequiresTypeProperty,
42
+ 'event-handler-requires-subscribed-events': eventHandlerRequiresSubscribedEvents,
43
+ 'ui-page-requires-route': uiPageRequiresRoute,
44
+ 'event-publisher-method-signature': eventPublisherMethodSignature,
45
+ 'http-client-requires-service-name': httpClientRequiresRemoteName,
46
+ 'http-call-requires-route': httpCallRequiresRoute,
47
+ 'http-call-requires-http-client-container': httpCallRequiresHttpClientContainer,
48
+ 'http-client-public-methods-require-http-call': httpClientPublicMethodsRequireHttpCall,
49
+ 'no-fetch-outside-http-client': noFetchOutsideHttpClient,
50
+ },
51
+ configs: {
52
+ 'http-client-import-boundary': {
53
+ rules: {
54
+ 'no-restricted-imports': [
55
+ 'error',
56
+ {
57
+ paths: restrictedHttpClientImports.map(toRestrictedImportPath),
58
+ patterns: restrictedHttpClientImports.map(toRestrictedImportPattern),
59
+ },
60
+ ],
61
+ },
62
+ },
63
+ },
64
+ }
@@ -0,0 +1,57 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ interface Plugin {
4
+ rules: {
5
+ 'require-component-decorator': TSESLint.RuleModule<'missingDecorator'>
6
+ 'api-controller-requires-route-and-method': TSESLint.RuleModule<
7
+ | 'missingRoute'
8
+ | 'missingMethod'
9
+ | 'routeNotLiteral'
10
+ | 'methodNotLiteral'
11
+ | 'invalidHttpMethod'
12
+ >
13
+ 'event-requires-type-property': TSESLint.RuleModule<'missingType' | 'typeNotLiteral'>
14
+ 'event-handler-requires-subscribed-events': TSESLint.RuleModule<
15
+ 'missingSubscribedEvents' | 'subscribedEventsNotLiteralArray'
16
+ >
17
+ 'ui-page-requires-route': TSESLint.RuleModule<'missingRoute' | 'routeNotLiteral'>
18
+ 'event-publisher-method-signature': TSESLint.RuleModule<
19
+ | 'missingParameter'
20
+ | 'tooManyParameters'
21
+ | 'missingTypeAnnotation'
22
+ | 'notTypeReference'
23
+ | 'invalidEventType'
24
+ | 'nonPublicMethod'
25
+ >
26
+ 'http-client-requires-service-name': TSESLint.RuleModule<
27
+ 'missingServiceName' | 'serviceNameNotLiteral' | 'emptyServiceName'
28
+ >
29
+ 'http-call-requires-route': TSESLint.RuleModule<
30
+ | 'missingRoute'
31
+ | 'missingMethod'
32
+ | 'routeNotLiteral'
33
+ | 'methodNotLiteral'
34
+ | 'emptyRoute'
35
+ | 'emptyMethod'
36
+ >
37
+ 'http-call-requires-http-client-container': TSESLint.RuleModule<'missingHttpClientContainer'>
38
+ 'http-client-public-methods-require-http-call': TSESLint.RuleModule<'missingHttpCall'>
39
+ 'no-fetch-outside-http-client': TSESLint.RuleModule<'fetchOutsideHttpClient'>
40
+ }
41
+ configs: {
42
+ 'http-client-import-boundary': {
43
+ rules: {
44
+ 'no-restricted-imports': [
45
+ 'error',
46
+ {
47
+ paths: Array<{ name: string; message: string }>
48
+ patterns: Array<{ group: string[]; message: string }>
49
+ },
50
+ ]
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ declare const plugin: Plugin
57
+ export default plugin
@@ -0,0 +1,83 @@
1
+ function hasDecorator(node, decoratorName) {
2
+ if (!node.decorators || node.decorators.length === 0) {
3
+ return false
4
+ }
5
+ return node.decorators.some(
6
+ (d) => d.expression.type === 'Identifier' && d.expression.name === decoratorName,
7
+ )
8
+ }
9
+
10
+ function findInstanceProperty(classNode, propertyName) {
11
+ /* v8 ignore next 3 -- ESLint guarantees ClassDeclaration.body exists per estree spec; structurally unreachable */
12
+ if (!classNode.body || !classNode.body.body) {
13
+ return null
14
+ }
15
+ return classNode.body.body.find((member) => {
16
+ return (
17
+ member.type === 'PropertyDefinition' &&
18
+ member.static !== true &&
19
+ member.key &&
20
+ member.key.type === 'Identifier' &&
21
+ member.key.name === propertyName
22
+ )
23
+ }) || null
24
+ }
25
+
26
+ function hasStringLiteralValue(property) {
27
+ /* v8 ignore next 3 -- callers (api-controller, ui-page rules) always guard with findInstanceProperty before calling; null property is structurally unreachable in current call sites */
28
+ if (!property || !property.value) {
29
+ return false
30
+ }
31
+ return property.value.type === 'Literal' && typeof property.value.value === 'string'
32
+ }
33
+
34
+ function hasStringLiteralArrayValue(property) {
35
+ /* v8 ignore next 3 -- callers always guard with findInstanceProperty before calling; null property is structurally unreachable in current call sites */
36
+ if (!property || !property.value) {
37
+ return false
38
+ }
39
+ if (property.value.type !== 'ArrayExpression') {
40
+ return false
41
+ }
42
+ return property.value.elements.every(
43
+ (element) => element && element.type === 'Literal' && typeof element.value === 'string'
44
+ )
45
+ }
46
+
47
+ function getLiteralValue(property) {
48
+ /* v8 ignore next 3 -- callers always guard with hasStringLiteralValue before calling; non-literal property is structurally unreachable in current call sites */
49
+ if (!property || !property.value || property.value.type !== 'Literal') {
50
+ return null
51
+ }
52
+ return property.value.value
53
+ }
54
+
55
+ function getValueTypeDescription(property) {
56
+ /* v8 ignore next 3 -- callers always guard with findInstanceProperty before calling; null property is structurally unreachable in current call sites */
57
+ if (!property || !property.value) {
58
+ return 'undefined'
59
+ }
60
+ const valueType = property.value.type
61
+ if (valueType === 'Identifier') {
62
+ return `variable reference '${property.value.name}'`
63
+ }
64
+ if (valueType === 'MemberExpression') {
65
+ return 'member expression (possibly an enum)'
66
+ }
67
+ if (valueType === 'CallExpression') {
68
+ return 'function call'
69
+ }
70
+ if (valueType === 'TemplateLiteral') {
71
+ return 'template literal'
72
+ }
73
+ return valueType
74
+ }
75
+
76
+ module.exports = {
77
+ hasDecorator,
78
+ findInstanceProperty,
79
+ hasStringLiteralValue,
80
+ hasStringLiteralArrayValue,
81
+ getLiteralValue,
82
+ getValueTypeDescription,
83
+ }
@@ -0,0 +1,60 @@
1
+ function hasDecoratorNamed(decorators, decoratorName) {
2
+ /* v8 ignore next 3 -- typescript-eslint provides an array for decorators on class and method nodes; undefined is structurally unreachable in current parser output */
3
+ if (!decorators) {
4
+ return false
5
+ }
6
+ return decorators.some((decorator) => {
7
+ const expression = decorator.expression
8
+ if (expression.type === 'Identifier') {
9
+ return expression.name === decoratorName
10
+ }
11
+ return (
12
+ expression.type === 'CallExpression' &&
13
+ expression.callee.type === 'Identifier' &&
14
+ expression.callee.name === decoratorName
15
+ )
16
+ })
17
+ }
18
+
19
+ function getEnclosingClassDeclaration(sourceCode, node) {
20
+ const ancestors = sourceCode.getAncestors(node)
21
+ for (let index = ancestors.length - 1; index >= 0; index -= 1) {
22
+ const ancestor = ancestors[index]
23
+ if (ancestor.type === 'ClassDeclaration') {
24
+ return ancestor
25
+ }
26
+ }
27
+ return null
28
+ }
29
+
30
+ module.exports = {
31
+ meta: {
32
+ type: 'problem',
33
+ docs: { description: 'Disallow fetch usage outside HttpClient classes' },
34
+ schema: [],
35
+ messages: {
36
+ fetchOutsideHttpClient:
37
+ "Call to '{{calleeName}}' is only allowed inside classes decorated with @HttpClient",
38
+ },
39
+ },
40
+ create(context) {
41
+ return {
42
+ CallExpression(node) {
43
+ if (node.callee.type !== 'Identifier' || node.callee.name !== 'fetch') {
44
+ return
45
+ }
46
+
47
+ const enclosingClass = getEnclosingClassDeclaration(context.sourceCode, node)
48
+ if (enclosingClass && hasDecoratorNamed(enclosingClass.decorators, 'HttpClient')) {
49
+ return
50
+ }
51
+
52
+ context.report({
53
+ node,
54
+ messageId: 'fetchOutsideHttpClient',
55
+ data: { calleeName: 'fetch' },
56
+ })
57
+ },
58
+ }
59
+ },
60
+ }
@@ -0,0 +1,5 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ declare const rule: TSESLint.RuleModule<string, readonly unknown[]>
4
+
5
+ export = rule
@@ -0,0 +1,56 @@
1
+ const COMPONENT_DECORATORS = [
2
+ 'DomainOpContainer',
3
+ 'APIContainer',
4
+ 'EventHandlerContainer',
5
+ 'EventPublisherContainer',
6
+ 'UseCase',
7
+ 'Event',
8
+ 'UI',
9
+ 'Ignore',
10
+ ]
11
+
12
+ const CALL_EXPRESSION_DECORATORS = ['Custom']
13
+
14
+ function getDecoratorName(expression) {
15
+ if (expression.type === 'Identifier') {
16
+ return expression.name
17
+ }
18
+ if (expression.type === 'CallExpression' && expression.callee.type === 'Identifier') {
19
+ return expression.callee.name
20
+ }
21
+ return null
22
+ }
23
+
24
+ function hasComponentDecorator(decorators) {
25
+ return decorators.some((decorator) => {
26
+ const name = getDecoratorName(decorator.expression)
27
+ if (name === null) return false
28
+ return COMPONENT_DECORATORS.includes(name) || CALL_EXPRESSION_DECORATORS.includes(name)
29
+ })
30
+ }
31
+
32
+ module.exports = {
33
+ meta: {
34
+ type: 'problem',
35
+ docs: { description: 'Require classes to have a component decorator' },
36
+ schema: [],
37
+ messages: {
38
+ missingDecorator:
39
+ "Class '{{className}}' requires a component decorator. Add one of: @UseCase, @Event, @UI, @DomainOpContainer, @APIContainer, @EventHandlerContainer, @EventPublisherContainer, @Custom('type'), or @Ignore",
40
+ },
41
+ },
42
+ create(context) {
43
+ return {
44
+ ClassDeclaration(node) {
45
+ if (!node.id) return
46
+ if (hasComponentDecorator(node.decorators)) return
47
+
48
+ context.report({
49
+ node: node.id,
50
+ messageId: 'missingDecorator',
51
+ data: { className: node.id.name },
52
+ })
53
+ },
54
+ }
55
+ },
56
+ }
@@ -0,0 +1,4 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ declare const rule: TSESLint.RuleModule<'missingDecorator'>
4
+ export default rule
@@ -0,0 +1,59 @@
1
+ type GenericRuleModule = import('@typescript-eslint/utils').TSESLint.RuleModule<
2
+ string,
3
+ readonly unknown[]
4
+ >
5
+
6
+ declare module './api-controller-requires-route-and-method.cjs' {
7
+ const rule: GenericRuleModule
8
+ export default rule
9
+ }
10
+
11
+ declare module './event-handler-requires-subscribed-events.cjs' {
12
+ const rule: GenericRuleModule
13
+ export default rule
14
+ }
15
+
16
+ declare module './event-publisher-method-signature.cjs' {
17
+ const rule: GenericRuleModule
18
+ export default rule
19
+ }
20
+
21
+ declare module './event-requires-type-property.cjs' {
22
+ const rule: GenericRuleModule
23
+ export default rule
24
+ }
25
+
26
+ declare module './http-call-requires-http-client-container.cjs' {
27
+ const rule: GenericRuleModule
28
+ export default rule
29
+ }
30
+
31
+ declare module './http-call-requires-route.cjs' {
32
+ const rule: GenericRuleModule
33
+ export default rule
34
+ }
35
+
36
+ declare module './http-client-public-methods-require-http-call.cjs' {
37
+ const rule: GenericRuleModule
38
+ export default rule
39
+ }
40
+
41
+ declare module './http-client-requires-remote-name.cjs' {
42
+ const rule: GenericRuleModule
43
+ export default rule
44
+ }
45
+
46
+ declare module './no-fetch-outside-http-client.cjs' {
47
+ const rule: GenericRuleModule
48
+ export default rule
49
+ }
50
+
51
+ declare module './require-component-decorator.cjs' {
52
+ const rule: GenericRuleModule
53
+ export default rule
54
+ }
55
+
56
+ declare module './ui-page-requires-route.cjs' {
57
+ const rule: GenericRuleModule
58
+ export default rule
59
+ }