@diia-inhouse/oxc-config 3.0.4 → 3.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../oxlint/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAsC,MAAM,QAAQ,CAAA;AAE9E,KAAK,MAAM,GAAG,YAAY,GAAG;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAiezD,eAAO,MAAM,IAAI,EAAE,MAAiD,CAAA;AAEpE,eAAO,MAAM,UAAU,EAAE,MAKb,CAAA;AAIZ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,YAAY,KAAG,YAOnD,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../oxlint/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAsC,MAAM,QAAQ,CAAA;AAE9E,KAAK,MAAM,GAAG,YAAY,GAAG;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AA2dzD,eAAO,MAAM,IAAI,EAAE,MAAiD,CAAA;AAEpE,eAAO,MAAM,UAAU,EAAE,MAKb,CAAA;AAIZ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,YAAY,KAAG,YAOnD,CAAA"}
@@ -3,9 +3,6 @@ import { defineConfig as defineOxlintConfig } from 'oxlint';
3
3
  const require = createRequire(import.meta.url);
4
4
  const resolve = (pkg) => require.resolve(pkg).replace(/\/[^/]+$/, '');
5
5
  const resolveFile = (path) => new URL(path, import.meta.url).pathname;
6
- // A package that ships `exports` without `main` cannot be loaded from its directory,
7
- // so it is pointed at its entry file instead of being stripped back by `resolve`.
8
- const resolveEntry = (pkg) => require.resolve(pkg);
9
6
  const baseConfig = {
10
7
  plugins: ['unicorn', 'typescript', 'import', 'promise', 'node', 'vitest', 'oxc'],
11
8
  jsPlugins: [
@@ -13,12 +10,12 @@ const baseConfig = {
13
10
  resolve('eslint-plugin-regexp'),
14
11
  { name: '@stylistic/js', specifier: resolve('@stylistic/eslint-plugin') },
15
12
  resolve('@diia-inhouse/eslint-plugin'),
16
- { name: 'ts-eslint', specifier: resolveEntry('@typescript-eslint/eslint-plugin') },
17
13
  { name: '@diia-inhouse/package', specifier: resolveFile('./plugins/packageCheck.mjs') },
18
14
  { name: '@diia-inhouse/locale', specifier: resolveFile('./plugins/noHardcodedCyrillic.mjs') },
19
15
  { name: '@diia-inhouse/test', specifier: resolveFile('./plugins/testConventions.mjs') },
20
16
  { name: '@diia-inhouse/mongoose', specifier: resolveFile('./plugins/mongooseSchema.mjs') },
21
17
  { name: '@diia-inhouse/class', specifier: resolveFile('./plugins/classEncapsulation.mjs') },
18
+ { name: '@diia-inhouse/class-conventions', specifier: resolveFile('./plugins/classConventions.mjs') },
22
19
  { name: '@diia-inhouse/temporal', specifier: resolveFile('./plugins/temporalConventions.mjs') },
23
20
  { name: '@diia-inhouse/code', specifier: resolveFile('./plugins/codeConventions.mjs') },
24
21
  ],
@@ -101,7 +98,7 @@ const baseConfig = {
101
98
  'unicorn/prefer-spread': 'off',
102
99
  'unicorn/require-post-message-target-origin': 'off',
103
100
  'unicorn/no-array-sort': 'off',
104
- 'unicorn/no-array-reduce': 'error',
101
+ 'unicorn/no-array-reduce': ['error', { allowSimpleOperations: false }],
105
102
  'promise/always-return': 'error',
106
103
  'promise/no-return-wrap': 'error',
107
104
  'promise/param-names': 'error',
@@ -204,9 +201,7 @@ const baseConfig = {
204
201
  '@diia-inhouse/mongoose/sub-schema-id-false': 'off',
205
202
  '@diia-inhouse/class/no-module-level-const': 'warn',
206
203
  '@diia-inhouse/class/no-interface-in-implementation': 'warn',
207
- // Carried by the ESLint config until the oxc migration; oxlint has no native
208
- // equivalent, so the original rule runs as a JS plugin at its original severity.
209
- 'ts-eslint/member-ordering': 'error',
204
+ '@diia-inhouse/class-conventions/member-ordering': 'error',
210
205
  '@diia-inhouse/temporal/workflow-single-param': 'error',
211
206
  '@diia-inhouse/temporal/async-activity': 'error',
212
207
  '@diia-inhouse/temporal/no-node-imports': 'error',
@@ -0,0 +1,282 @@
1
+ const accessibilities = ['public', 'protected', 'private', '#private']
2
+
3
+ const groupsOf = (type) => [
4
+ ...accessibilities.map((accessibility) => `${accessibility}-static-${type}`),
5
+ ...accessibilities.slice(0, 3).map((accessibility) => `${accessibility}-decorated-${type}`),
6
+ ...accessibilities.map((accessibility) => `${accessibility}-instance-${type}`),
7
+ ...accessibilities.slice(0, 2).map((accessibility) => `${accessibility}-abstract-${type}`),
8
+ ...accessibilities.map((accessibility) => `${accessibility}-${type}`),
9
+ `static-${type}`,
10
+ `instance-${type}`,
11
+ `abstract-${type}`,
12
+ `decorated-${type}`,
13
+ type,
14
+ ]
15
+
16
+ // Mirrors the default order of @typescript-eslint/member-ordering, which the eslint config enforced before the oxc migration
17
+ const memberOrder = [
18
+ 'signature',
19
+ 'call-signature',
20
+ ...groupsOf('field'),
21
+ 'static-initialization',
22
+ 'public-constructor',
23
+ 'protected-constructor',
24
+ 'private-constructor',
25
+ 'constructor',
26
+ ...groupsOf('accessor'),
27
+ ...groupsOf('get'),
28
+ ...groupsOf('set'),
29
+ ...groupsOf('method'),
30
+ ]
31
+
32
+ const rankByGroup = new Map(memberOrder.map((group, rank) => [group, rank]))
33
+
34
+ const typeOf = (member) => {
35
+ switch (member.type) {
36
+ case 'TSIndexSignature': {
37
+ return 'signature'
38
+ }
39
+ case 'TSCallSignatureDeclaration': {
40
+ return 'call-signature'
41
+ }
42
+ case 'TSConstructSignatureDeclaration': {
43
+ return 'constructor'
44
+ }
45
+ case 'StaticBlock': {
46
+ return 'static-initialization'
47
+ }
48
+ case 'AccessorProperty':
49
+ case 'TSAbstractAccessorProperty': {
50
+ return 'accessor'
51
+ }
52
+ case 'TSPropertySignature': {
53
+ return 'field'
54
+ }
55
+ case 'PropertyDefinition':
56
+ case 'TSAbstractPropertyDefinition': {
57
+ return ['ArrowFunctionExpression', 'FunctionExpression'].includes(member.value?.type) ? 'method' : 'field'
58
+ }
59
+ case 'MethodDefinition': {
60
+ // Overload signatures can't be decorated, so ranking them would split a decorated implementation from its overloads
61
+ return member.value.type === 'TSEmptyBodyFunctionExpression' ? undefined : member.kind
62
+ }
63
+ case 'TSAbstractMethodDefinition':
64
+ case 'TSMethodSignature': {
65
+ return member.kind
66
+ }
67
+ default: {
68
+ return undefined
69
+ }
70
+ }
71
+ }
72
+
73
+ const groupsOfMember = (member, type, supportsModifiers) => {
74
+ if (!supportsModifiers || type === 'signature' || type === 'static-initialization') {
75
+ return [type]
76
+ }
77
+
78
+ const accessibility = member.key.type === 'PrivateIdentifier' ? '#private' : (member.accessibility ?? 'public')
79
+ if (type === 'constructor') {
80
+ return [`${accessibility}-constructor`, 'constructor']
81
+ }
82
+
83
+ const scope = member.static ? 'static' : member.type.startsWith('TSAbstract') ? 'abstract' : 'instance'
84
+ const decorated = member.decorators?.length > 0
85
+
86
+ return [
87
+ ...(decorated ? [`${accessibility}-decorated-${type}`, `decorated-${type}`] : []),
88
+ `${accessibility}-${scope}-${type}`,
89
+ `${scope}-${type}`,
90
+ `${accessibility}-${type}`,
91
+ type,
92
+ ]
93
+ }
94
+
95
+ const rankOf = (member, supportsModifiers) => {
96
+ const type = typeOf(member)
97
+ if (!type) {
98
+ return -1
99
+ }
100
+
101
+ return rankByGroup.get(groupsOfMember(member, type, supportsModifiers).find((group) => rankByGroup.has(group)))
102
+ }
103
+
104
+ const hasInitializer = (member) => ['PropertyDefinition', 'AccessorProperty'].includes(member.type) && Boolean(member.value)
105
+
106
+ const initializes = (member) => member.type === 'StaticBlock' || hasInitializer(member)
107
+
108
+ const isStatic = (member) => member.type === 'StaticBlock' || Boolean(member.static)
109
+
110
+ const hasBlockBody = (member) =>
111
+ member.type === 'StaticBlock' || (member.type === 'MethodDefinition' && member.value.type !== 'TSEmptyBodyFunctionExpression')
112
+
113
+ const keylessMemberNames = {
114
+ StaticBlock: 'static block',
115
+ TSIndexSignature: 'index signature',
116
+ TSCallSignatureDeclaration: 'call',
117
+ TSConstructSignatureDeclaration: 'new',
118
+ }
119
+
120
+ const disableDirective = /^\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?(?:\s+([^]*?))?\s*(?:--|$)/
121
+
122
+ const disablesOrdering = (comment) => {
123
+ const match = disableDirective.exec(comment.value)
124
+
125
+ return Boolean(match) && (!match[1] || match[1].includes('member-ordering'))
126
+ }
127
+
128
+ export default {
129
+ meta: { name: 'class-conventions' },
130
+ rules: {
131
+ 'member-ordering': {
132
+ meta: {
133
+ type: 'suggestion',
134
+ fixable: 'code',
135
+ hasSuggestions: true,
136
+ messages: {
137
+ incorrectGroupOrder: 'Member {{ name }} should be declared before all {{ group }} definitions.',
138
+ sortMembers: 'Sort members by group. This changes the order in which field initializers run.',
139
+ sortExemptMembers: 'Sort members by group. This also moves members exempted by a disable comment.',
140
+ },
141
+ },
142
+ create(context) {
143
+ const { sourceCode } = context
144
+
145
+ const chunkOf = (member, previousEnd) => {
146
+ const leading = sourceCode
147
+ .getCommentsBefore(member)
148
+ .find((comment) => sourceCode.text.slice(previousEnd, comment.range[0]).includes('\n'))
149
+ const trailing = sourceCode
150
+ .getCommentsAfter(member)
151
+ .findLast((comment) => !sourceCode.text.slice(member.range[1], comment.range[0]).includes('\n'))
152
+
153
+ return { start: leading?.range[0] ?? member.range[0], end: trailing?.range[1] ?? member.range[1] }
154
+ }
155
+
156
+ // A moved field can end up before `[computed]`, `*generator`, `in()` or `instanceof()`, which would continue its initializer expression
157
+ const needsSemicolon = (member, next, gap) => {
158
+ if (hasBlockBody(member) || [',', ';'].includes(sourceCode.getText(member).at(-1))) {
159
+ return false
160
+ }
161
+
162
+ return !gap.includes('\n') || (hasInitializer(member) && /^(?:[*[]|in\b|instanceof\b)/.test(sourceCode.getText(next)))
163
+ }
164
+
165
+ const sortFixOf = (body, members, supportsModifiers) => {
166
+ const chunks = members.map((member, index) =>
167
+ chunkOf(member, index === 0 ? body.range[0] + 1 : members[index - 1].range[1]),
168
+ )
169
+
170
+ const units = []
171
+ let first = 0
172
+ for (const [index, member] of members.entries()) {
173
+ const rank = rankOf(member, supportsModifiers)
174
+ if (rank !== -1) {
175
+ units.push({ first, last: index, rank })
176
+ first = index + 1
177
+ }
178
+ }
179
+
180
+ units.at(-1).last = members.length - 1
181
+
182
+ const sorted = units.toSorted((a, b) => a.rank - b.rank)
183
+ const gaps = units
184
+ .slice(1)
185
+ .map((unit, index) => sourceCode.text.slice(chunks[units[index].last].end, chunks[unit.first].start))
186
+ const textOf = (unit, next, gap) => {
187
+ const text = sourceCode.text.slice(chunks[unit.first].start, chunks[unit.last].end)
188
+ if (!next || !needsSemicolon(members[unit.last], members[next.first], gap)) {
189
+ return text
190
+ }
191
+
192
+ const semicolonAt = members[unit.last].range[1] - chunks[unit.first].start
193
+
194
+ return `${text.slice(0, semicolonAt)};${text.slice(semicolonAt)}`
195
+ }
196
+
197
+ const replacement = sorted
198
+ .map((unit, index) => textOf(unit, sorted[index + 1], gaps[index]) + (gaps[index] ?? ''))
199
+ .join('')
200
+ const fix = (fixer) => fixer.replaceTextRange([chunks[units[0].first].start, chunks.at(-1).end], replacement)
201
+
202
+ const initializationOrderOf = (ordered) =>
203
+ ordered
204
+ .flatMap((unit) => members.slice(unit.first, unit.last + 1))
205
+ .filter(initializes)
206
+ .toSorted((a, b) => Number(isStatic(a)) - Number(isStatic(b)))
207
+ const sortedInitializationOrder = initializationOrderOf(sorted)
208
+ const keepsInitializationOrder = initializationOrderOf(units).every(
209
+ (member, index) => member === sortedInitializationOrder[index],
210
+ )
211
+
212
+ if (!keepsInitializationOrder) {
213
+ return { suggest: [{ messageId: 'sortMembers', fix }] }
214
+ }
215
+
216
+ // Sorting the whole body would override a member someone deliberately exempted from the rule
217
+ if (sourceCode.getCommentsInside(body).some(disablesOrdering)) {
218
+ return { suggest: [{ messageId: 'sortExemptMembers', fix }] }
219
+ }
220
+
221
+ return { fix }
222
+ }
223
+
224
+ const nameOf = (member) => {
225
+ if (member.type in keylessMemberNames) {
226
+ return keylessMemberNames[member.type]
227
+ }
228
+
229
+ if (member.key.type === 'PrivateIdentifier') {
230
+ return `#${member.key.name}`
231
+ }
232
+
233
+ return member.key.type === 'Identifier' && !member.computed ? member.key.name : context.sourceCode.getText(member.key)
234
+ }
235
+
236
+ const checkOrder = (body, members, supportsModifiers) => {
237
+ const ranks = []
238
+ const misplaced = []
239
+ for (const member of members) {
240
+ const rank = rankOf(member, supportsModifiers)
241
+ if (rank === -1) {
242
+ continue
243
+ }
244
+
245
+ if (ranks.length === 0 || rank > ranks.at(-1)) {
246
+ ranks.push(rank)
247
+ } else if (rank < ranks.at(-1)) {
248
+ misplaced.push({ member, expected: Math.min(...ranks.filter((previous) => previous > rank)) })
249
+ }
250
+ }
251
+
252
+ if (misplaced.length === 0) {
253
+ return
254
+ }
255
+
256
+ // Every report carries the fix, so it survives when some of them are suppressed by a disable comment
257
+ const sortFix = sortFixOf(body, members, supportsModifiers)
258
+ for (const { member, expected } of misplaced) {
259
+ context.report({
260
+ node: member,
261
+ messageId: 'incorrectGroupOrder',
262
+ data: { name: nameOf(member), group: memberOrder[expected].replaceAll('-', ' ') },
263
+ ...sortFix,
264
+ })
265
+ }
266
+ }
267
+
268
+ return {
269
+ ClassBody(node) {
270
+ checkOrder(node, node.body, true)
271
+ },
272
+ TSInterfaceBody(node) {
273
+ checkOrder(node, node.body, false)
274
+ },
275
+ TSTypeLiteral(node) {
276
+ checkOrder(node, node.members, false)
277
+ },
278
+ }
279
+ },
280
+ },
281
+ },
282
+ }
@@ -0,0 +1,282 @@
1
+ const accessibilities = ['public', 'protected', 'private', '#private']
2
+
3
+ const groupsOf = (type) => [
4
+ ...accessibilities.map((accessibility) => `${accessibility}-static-${type}`),
5
+ ...accessibilities.slice(0, 3).map((accessibility) => `${accessibility}-decorated-${type}`),
6
+ ...accessibilities.map((accessibility) => `${accessibility}-instance-${type}`),
7
+ ...accessibilities.slice(0, 2).map((accessibility) => `${accessibility}-abstract-${type}`),
8
+ ...accessibilities.map((accessibility) => `${accessibility}-${type}`),
9
+ `static-${type}`,
10
+ `instance-${type}`,
11
+ `abstract-${type}`,
12
+ `decorated-${type}`,
13
+ type,
14
+ ]
15
+
16
+ // Mirrors the default order of @typescript-eslint/member-ordering, which the eslint config enforced before the oxc migration
17
+ const memberOrder = [
18
+ 'signature',
19
+ 'call-signature',
20
+ ...groupsOf('field'),
21
+ 'static-initialization',
22
+ 'public-constructor',
23
+ 'protected-constructor',
24
+ 'private-constructor',
25
+ 'constructor',
26
+ ...groupsOf('accessor'),
27
+ ...groupsOf('get'),
28
+ ...groupsOf('set'),
29
+ ...groupsOf('method'),
30
+ ]
31
+
32
+ const rankByGroup = new Map(memberOrder.map((group, rank) => [group, rank]))
33
+
34
+ const typeOf = (member) => {
35
+ switch (member.type) {
36
+ case 'TSIndexSignature': {
37
+ return 'signature'
38
+ }
39
+ case 'TSCallSignatureDeclaration': {
40
+ return 'call-signature'
41
+ }
42
+ case 'TSConstructSignatureDeclaration': {
43
+ return 'constructor'
44
+ }
45
+ case 'StaticBlock': {
46
+ return 'static-initialization'
47
+ }
48
+ case 'AccessorProperty':
49
+ case 'TSAbstractAccessorProperty': {
50
+ return 'accessor'
51
+ }
52
+ case 'TSPropertySignature': {
53
+ return 'field'
54
+ }
55
+ case 'PropertyDefinition':
56
+ case 'TSAbstractPropertyDefinition': {
57
+ return ['ArrowFunctionExpression', 'FunctionExpression'].includes(member.value?.type) ? 'method' : 'field'
58
+ }
59
+ case 'MethodDefinition': {
60
+ // Overload signatures can't be decorated, so ranking them would split a decorated implementation from its overloads
61
+ return member.value.type === 'TSEmptyBodyFunctionExpression' ? undefined : member.kind
62
+ }
63
+ case 'TSAbstractMethodDefinition':
64
+ case 'TSMethodSignature': {
65
+ return member.kind
66
+ }
67
+ default: {
68
+ return undefined
69
+ }
70
+ }
71
+ }
72
+
73
+ const groupsOfMember = (member, type, supportsModifiers) => {
74
+ if (!supportsModifiers || type === 'signature' || type === 'static-initialization') {
75
+ return [type]
76
+ }
77
+
78
+ const accessibility = member.key.type === 'PrivateIdentifier' ? '#private' : (member.accessibility ?? 'public')
79
+ if (type === 'constructor') {
80
+ return [`${accessibility}-constructor`, 'constructor']
81
+ }
82
+
83
+ const scope = member.static ? 'static' : member.type.startsWith('TSAbstract') ? 'abstract' : 'instance'
84
+ const decorated = member.decorators?.length > 0
85
+
86
+ return [
87
+ ...(decorated ? [`${accessibility}-decorated-${type}`, `decorated-${type}`] : []),
88
+ `${accessibility}-${scope}-${type}`,
89
+ `${scope}-${type}`,
90
+ `${accessibility}-${type}`,
91
+ type,
92
+ ]
93
+ }
94
+
95
+ const rankOf = (member, supportsModifiers) => {
96
+ const type = typeOf(member)
97
+ if (!type) {
98
+ return -1
99
+ }
100
+
101
+ return rankByGroup.get(groupsOfMember(member, type, supportsModifiers).find((group) => rankByGroup.has(group)))
102
+ }
103
+
104
+ const hasInitializer = (member) => ['PropertyDefinition', 'AccessorProperty'].includes(member.type) && Boolean(member.value)
105
+
106
+ const initializes = (member) => member.type === 'StaticBlock' || hasInitializer(member)
107
+
108
+ const isStatic = (member) => member.type === 'StaticBlock' || Boolean(member.static)
109
+
110
+ const hasBlockBody = (member) =>
111
+ member.type === 'StaticBlock' || (member.type === 'MethodDefinition' && member.value.type !== 'TSEmptyBodyFunctionExpression')
112
+
113
+ const keylessMemberNames = {
114
+ StaticBlock: 'static block',
115
+ TSIndexSignature: 'index signature',
116
+ TSCallSignatureDeclaration: 'call',
117
+ TSConstructSignatureDeclaration: 'new',
118
+ }
119
+
120
+ const disableDirective = /^\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?(?:\s+([^]*?))?\s*(?:--|$)/
121
+
122
+ const disablesOrdering = (comment) => {
123
+ const match = disableDirective.exec(comment.value)
124
+
125
+ return Boolean(match) && (!match[1] || match[1].includes('member-ordering'))
126
+ }
127
+
128
+ export default {
129
+ meta: { name: 'class-conventions' },
130
+ rules: {
131
+ 'member-ordering': {
132
+ meta: {
133
+ type: 'suggestion',
134
+ fixable: 'code',
135
+ hasSuggestions: true,
136
+ messages: {
137
+ incorrectGroupOrder: 'Member {{ name }} should be declared before all {{ group }} definitions.',
138
+ sortMembers: 'Sort members by group. This changes the order in which field initializers run.',
139
+ sortExemptMembers: 'Sort members by group. This also moves members exempted by a disable comment.',
140
+ },
141
+ },
142
+ create(context) {
143
+ const { sourceCode } = context
144
+
145
+ const chunkOf = (member, previousEnd) => {
146
+ const leading = sourceCode
147
+ .getCommentsBefore(member)
148
+ .find((comment) => sourceCode.text.slice(previousEnd, comment.range[0]).includes('\n'))
149
+ const trailing = sourceCode
150
+ .getCommentsAfter(member)
151
+ .findLast((comment) => !sourceCode.text.slice(member.range[1], comment.range[0]).includes('\n'))
152
+
153
+ return { start: leading?.range[0] ?? member.range[0], end: trailing?.range[1] ?? member.range[1] }
154
+ }
155
+
156
+ // A moved field can end up before `[computed]`, `*generator`, `in()` or `instanceof()`, which would continue its initializer expression
157
+ const needsSemicolon = (member, next, gap) => {
158
+ if (hasBlockBody(member) || [',', ';'].includes(sourceCode.getText(member).at(-1))) {
159
+ return false
160
+ }
161
+
162
+ return !gap.includes('\n') || (hasInitializer(member) && /^(?:[*[]|in\b|instanceof\b)/.test(sourceCode.getText(next)))
163
+ }
164
+
165
+ const sortFixOf = (body, members, supportsModifiers) => {
166
+ const chunks = members.map((member, index) =>
167
+ chunkOf(member, index === 0 ? body.range[0] + 1 : members[index - 1].range[1]),
168
+ )
169
+
170
+ const units = []
171
+ let first = 0
172
+ for (const [index, member] of members.entries()) {
173
+ const rank = rankOf(member, supportsModifiers)
174
+ if (rank !== -1) {
175
+ units.push({ first, last: index, rank })
176
+ first = index + 1
177
+ }
178
+ }
179
+
180
+ units.at(-1).last = members.length - 1
181
+
182
+ const sorted = units.toSorted((a, b) => a.rank - b.rank)
183
+ const gaps = units
184
+ .slice(1)
185
+ .map((unit, index) => sourceCode.text.slice(chunks[units[index].last].end, chunks[unit.first].start))
186
+ const textOf = (unit, next, gap) => {
187
+ const text = sourceCode.text.slice(chunks[unit.first].start, chunks[unit.last].end)
188
+ if (!next || !needsSemicolon(members[unit.last], members[next.first], gap)) {
189
+ return text
190
+ }
191
+
192
+ const semicolonAt = members[unit.last].range[1] - chunks[unit.first].start
193
+
194
+ return `${text.slice(0, semicolonAt)};${text.slice(semicolonAt)}`
195
+ }
196
+
197
+ const replacement = sorted
198
+ .map((unit, index) => textOf(unit, sorted[index + 1], gaps[index]) + (gaps[index] ?? ''))
199
+ .join('')
200
+ const fix = (fixer) => fixer.replaceTextRange([chunks[units[0].first].start, chunks.at(-1).end], replacement)
201
+
202
+ const initializationOrderOf = (ordered) =>
203
+ ordered
204
+ .flatMap((unit) => members.slice(unit.first, unit.last + 1))
205
+ .filter(initializes)
206
+ .toSorted((a, b) => Number(isStatic(a)) - Number(isStatic(b)))
207
+ const sortedInitializationOrder = initializationOrderOf(sorted)
208
+ const keepsInitializationOrder = initializationOrderOf(units).every(
209
+ (member, index) => member === sortedInitializationOrder[index],
210
+ )
211
+
212
+ if (!keepsInitializationOrder) {
213
+ return { suggest: [{ messageId: 'sortMembers', fix }] }
214
+ }
215
+
216
+ // Sorting the whole body would override a member someone deliberately exempted from the rule
217
+ if (sourceCode.getCommentsInside(body).some(disablesOrdering)) {
218
+ return { suggest: [{ messageId: 'sortExemptMembers', fix }] }
219
+ }
220
+
221
+ return { fix }
222
+ }
223
+
224
+ const nameOf = (member) => {
225
+ if (member.type in keylessMemberNames) {
226
+ return keylessMemberNames[member.type]
227
+ }
228
+
229
+ if (member.key.type === 'PrivateIdentifier') {
230
+ return `#${member.key.name}`
231
+ }
232
+
233
+ return member.key.type === 'Identifier' && !member.computed ? member.key.name : context.sourceCode.getText(member.key)
234
+ }
235
+
236
+ const checkOrder = (body, members, supportsModifiers) => {
237
+ const ranks = []
238
+ const misplaced = []
239
+ for (const member of members) {
240
+ const rank = rankOf(member, supportsModifiers)
241
+ if (rank === -1) {
242
+ continue
243
+ }
244
+
245
+ if (ranks.length === 0 || rank > ranks.at(-1)) {
246
+ ranks.push(rank)
247
+ } else if (rank < ranks.at(-1)) {
248
+ misplaced.push({ member, expected: Math.min(...ranks.filter((previous) => previous > rank)) })
249
+ }
250
+ }
251
+
252
+ if (misplaced.length === 0) {
253
+ return
254
+ }
255
+
256
+ // Every report carries the fix, so it survives when some of them are suppressed by a disable comment
257
+ const sortFix = sortFixOf(body, members, supportsModifiers)
258
+ for (const { member, expected } of misplaced) {
259
+ context.report({
260
+ node: member,
261
+ messageId: 'incorrectGroupOrder',
262
+ data: { name: nameOf(member), group: memberOrder[expected].replaceAll('-', ' ') },
263
+ ...sortFix,
264
+ })
265
+ }
266
+ }
267
+
268
+ return {
269
+ ClassBody(node) {
270
+ checkOrder(node, node.body, true)
271
+ },
272
+ TSInterfaceBody(node) {
273
+ checkOrder(node, node.body, false)
274
+ },
275
+ TSTypeLiteral(node) {
276
+ checkOrder(node, node.members, false)
277
+ },
278
+ }
279
+ },
280
+ },
281
+ },
282
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diia-inhouse/oxc-config",
3
- "version": "3.0.4",
3
+ "version": "3.2.0",
4
4
  "description": "OXC toolchain configs for Diia services: oxlint, oxfmt, oxc transformer",
5
5
  "author": "Diia",
6
6
  "type": "module",
@@ -33,7 +33,6 @@
33
33
  "@boundaries/eslint-plugin": "6.0.2",
34
34
  "@diia-inhouse/eslint-plugin": "1.9.13",
35
35
  "@stylistic/eslint-plugin": "5.10.0",
36
- "@typescript-eslint/eslint-plugin": "8.60.1",
37
36
  "eslint-import-resolver-oxc": "0.15.0",
38
37
  "eslint-plugin-regexp": "3.1.0",
39
38
  "eslint-plugin-security": "4.0.0",