@living-architecture/riviere-extract-conventions 0.4.4 → 0.4.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@living-architecture/riviere-extract-conventions",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,6 +32,6 @@
32
32
  "!**/*.tsbuildinfo"
33
33
  ],
34
34
  "dependencies": {
35
- "@living-architecture/riviere-extract-config": "0.4.3"
35
+ "@living-architecture/riviere-extract-config": "0.4.4"
36
36
  }
37
37
  }
@@ -0,0 +1,179 @@
1
+ const { implementsInterface } = require('./interface-ast-predicates.cjs')
2
+
3
+ let getParserServices = null
4
+ try {
5
+ getParserServices = require('@typescript-eslint/utils').ESLintUtils.getParserServices
6
+ /* v8 ignore next -- soft-require: package always present in this repo */
7
+ } catch { /* type checking unavailable */ }
8
+
9
+ function getTypeReferenceName(typeAnnotation) {
10
+ const typeName = typeAnnotation.typeName
11
+ if (typeName.type === 'TSQualifiedName') {
12
+ return typeName.right.name
13
+ }
14
+ return typeName.name
15
+ }
16
+
17
+ function checkEventDefShape(checker, services, param, typeAnnotation) {
18
+ if (!checker || !services) return null
19
+
20
+ const tsParam = services.esTreeNodeToTSNodeMap.get(param)
21
+ const paramType = checker.getTypeAtLocation(tsParam)
22
+ const typeProperty = paramType.getProperty('type')
23
+ const referenceName = getTypeReferenceName(typeAnnotation)
24
+
25
+ if (!typeProperty) {
26
+ return referenceName
27
+ }
28
+
29
+ const typePropertyType = checker.getTypeOfSymbol(typeProperty)
30
+ const stringType = checker.getStringType()
31
+ if (!checker.isTypeAssignableTo(typePropertyType, stringType)) {
32
+ return referenceName
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ function checkMethod(context, member, className, checker, services) {
39
+ const methodName = member.key.name
40
+
41
+ if (member.accessibility === 'private' || member.accessibility === 'protected') {
42
+ context.report({
43
+ node: member.key,
44
+ messageId: 'nonPublicMethod',
45
+ data: {
46
+ methodName,
47
+ className,
48
+ accessibility: member.accessibility,
49
+ },
50
+ })
51
+ return
52
+ }
53
+
54
+ const params = member.value.params
55
+
56
+ if (params.length === 0) {
57
+ context.report({
58
+ node: member.key,
59
+ messageId: 'missingParameter',
60
+ data: {
61
+ methodName,
62
+ className,
63
+ },
64
+ })
65
+ return
66
+ }
67
+
68
+ if (params.length > 1) {
69
+ context.report({
70
+ node: member.key,
71
+ messageId: 'tooManyParameters',
72
+ data: {
73
+ methodName,
74
+ className,
75
+ },
76
+ })
77
+ return
78
+ }
79
+
80
+ const param = params[0]
81
+ const typeAnnotation = param.typeAnnotation && param.typeAnnotation.typeAnnotation
82
+
83
+ if (!typeAnnotation) {
84
+ context.report({
85
+ node: member.key,
86
+ messageId: 'missingTypeAnnotation',
87
+ data: {
88
+ methodName,
89
+ className,
90
+ },
91
+ })
92
+ return
93
+ }
94
+
95
+ if (typeAnnotation.type !== 'TSTypeReference') {
96
+ context.report({
97
+ node: member.key,
98
+ messageId: 'notTypeReference',
99
+ data: {
100
+ methodName,
101
+ className,
102
+ },
103
+ })
104
+ return
105
+ }
106
+
107
+ const failedTypeName = checkEventDefShape(checker, services, param, typeAnnotation)
108
+ if (failedTypeName) {
109
+ context.report({
110
+ node: member.key,
111
+ messageId: 'notEventDef',
112
+ data: {
113
+ typeName: failedTypeName,
114
+ methodName,
115
+ className,
116
+ },
117
+ })
118
+ }
119
+ }
120
+
121
+ function isMethodToCheck(member) {
122
+ return (
123
+ member.type === 'MethodDefinition' &&
124
+ member.kind !== 'constructor' &&
125
+ member.kind !== 'get' &&
126
+ member.kind !== 'set'
127
+ )
128
+ }
129
+
130
+ module.exports = {
131
+ meta: {
132
+ type: 'problem',
133
+ docs: {
134
+ description: 'Require EventPublisherDef methods to have exactly one EventDef-typed parameter',
135
+ requiresTypeChecking: true,
136
+ examples: {
137
+ valid: "class OrderPublisher implements EventPublisherDef { publish(event: OrderPlacedEvent): void {} }",
138
+ invalid: "class OrderPublisher implements EventPublisherDef { publish(): void {} }",
139
+ },
140
+ },
141
+ schema: [],
142
+ messages: {
143
+ missingParameter: "Method '{{methodName}}' on EventPublisherDef class '{{className}}' must have exactly one parameter",
144
+ tooManyParameters: "Method '{{methodName}}' on EventPublisherDef class '{{className}}' must have exactly one parameter",
145
+ missingTypeAnnotation: "Parameter of method '{{methodName}}' on EventPublisherDef class '{{className}}' must have a type annotation",
146
+ notTypeReference: "Parameter of method '{{methodName}}' on EventPublisherDef class '{{className}}' must have a type reference annotation",
147
+ notEventDef: "Parameter type '{{typeName}}' of method '{{methodName}}' on EventPublisherDef class '{{className}}' does not implement EventDef (missing 'type: string' property)",
148
+ nonPublicMethod: "Method '{{methodName}}' on EventPublisherDef class '{{className}}' must be public, but is {{accessibility}}",
149
+ },
150
+ },
151
+ create(context) {
152
+ let services = null
153
+ let checker = null
154
+ /* v8 ignore start -- graceful degradation: getParserServices always available in this repo */
155
+ if (getParserServices) {
156
+ try {
157
+ services = getParserServices(context, true)
158
+ checker = services.program?.getTypeChecker() ?? null
159
+ } catch { /* no type info */ }
160
+ }
161
+ /* v8 ignore stop */
162
+
163
+ return {
164
+ ClassDeclaration(node) {
165
+ /* v8 ignore next -- ClassDeclaration always has id (name) */
166
+ if (!node.id) return
167
+ if (!implementsInterface(node, 'EventPublisherDef')) return
168
+
169
+ const className = node.id.name
170
+
171
+ for (const member of node.body.body) {
172
+ if (isMethodToCheck(member)) {
173
+ checkMethod(context, member, className, checker, services)
174
+ }
175
+ }
176
+ },
177
+ }
178
+ },
179
+ }
@@ -0,0 +1,6 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils'
2
+
3
+ type MessageIds = 'missingParameter' | 'tooManyParameters' | 'missingTypeAnnotation' | 'notTypeReference' | 'notEventDef' | 'nonPublicMethod'
4
+
5
+ declare const rule: TSESLint.RuleModule<MessageIds>
6
+ export default rule
@@ -3,6 +3,7 @@ const apiControllerRequiresRouteAndMethod = require('./api-controller-requires-r
3
3
  const eventRequiresTypeProperty = require('./event-requires-type-property.cjs')
4
4
  const eventHandlerRequiresSubscribedEvents = require('./event-handler-requires-subscribed-events.cjs')
5
5
  const uiPageRequiresRoute = require('./ui-page-requires-route.cjs')
6
+ const eventPublisherMethodSignature = require('./event-publisher-method-signature.cjs')
6
7
 
7
8
  module.exports = {
8
9
  rules: {
@@ -11,5 +12,6 @@ module.exports = {
11
12
  'event-requires-type-property': eventRequiresTypeProperty,
12
13
  'event-handler-requires-subscribed-events': eventHandlerRequiresSubscribedEvents,
13
14
  'ui-page-requires-route': uiPageRequiresRoute,
15
+ 'event-publisher-method-signature': eventPublisherMethodSignature,
14
16
  },
15
17
  }