@trigen/oxlint-config 9.2.2 → 9.3.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@trigen/oxlint-config",
3
3
  "type": "module",
4
- "version": "9.2.2",
4
+ "version": "9.3.0",
5
5
  "description": "Trigen's Oxlint config.",
6
6
  "author": "dangreen",
7
7
  "license": "MIT",
@@ -1,6 +1,7 @@
1
1
  import extensionsRule from './extensions.js'
2
2
  import importOrderRule from './import-order.js'
3
3
  import memberOrderingRule from './member-ordering.js'
4
+ import namedExportOrderRule from './named-export-order.js'
4
5
  import namedImportOrderRule from './named-import-order.js'
5
6
  import namingConventionRule from './naming-convention.js'
6
7
  import typeImportStyleRule from './type-import-style.js'
@@ -13,6 +14,7 @@ export default {
13
14
  'extensions': extensionsRule,
14
15
  'import-order': importOrderRule,
15
16
  'member-ordering': memberOrderingRule,
17
+ 'named-export-order': namedExportOrderRule,
16
18
  'named-import-order': namedImportOrderRule,
17
19
  'naming-convention': namingConventionRule,
18
20
  'type-import-style': typeImportStyleRule
@@ -0,0 +1,249 @@
1
+ const defaultOptions = {
2
+ typeExports: 'none',
3
+ patterns: []
4
+ }
5
+
6
+ function getOptions(context) {
7
+ return {
8
+ ...defaultOptions,
9
+ ...context.options[0],
10
+ patterns: context.options[0]?.patterns ?? []
11
+ }
12
+ }
13
+
14
+ function getName(specifier) {
15
+ const exported = specifier.exported ?? specifier.local
16
+
17
+ if (exported.type === 'Identifier') {
18
+ return exported.name
19
+ }
20
+
21
+ return String(exported.value)
22
+ }
23
+
24
+ function isTypeSpecifier(node, specifier) {
25
+ return node.exportKind === 'type'
26
+ || specifier.exportKind === 'type'
27
+ }
28
+
29
+ function getTypeRank(node, specifier, options) {
30
+ if (options.typeExports === 'first') {
31
+ return isTypeSpecifier(node, specifier) ? 0 : 1
32
+ }
33
+
34
+ if (options.typeExports === 'last') {
35
+ return isTypeSpecifier(node, specifier) ? 1 : 0
36
+ }
37
+
38
+ return 0
39
+ }
40
+
41
+ function getPatternRank(name, patterns) {
42
+ const normalizedName = name.replace(/^\$+|\$+$/g, '')
43
+ const rank = patterns.findIndex(pattern => new RegExp(pattern).test(
44
+ normalizedName
45
+ ))
46
+
47
+ return rank === -1 ? Number.POSITIVE_INFINITY : rank
48
+ }
49
+
50
+ function getRank(node, specifier, options) {
51
+ const name = getName(specifier)
52
+
53
+ return {
54
+ name,
55
+ typeRank: getTypeRank(node, specifier, options),
56
+ patternRank: getPatternRank(name, options.patterns)
57
+ }
58
+ }
59
+
60
+ function compareRanks(left, right) {
61
+ return left.typeRank - right.typeRank
62
+ || left.patternRank - right.patternRank
63
+ }
64
+
65
+ function getItems(node, options) {
66
+ return node.specifiers
67
+ .filter(specifier => specifier.type === 'ExportSpecifier')
68
+ .map((specifier, index) => ({
69
+ index,
70
+ specifier,
71
+ rank: getRank(node, specifier, options)
72
+ }))
73
+ }
74
+
75
+ function compareItems(left, right) {
76
+ return compareRanks(left.rank, right.rank)
77
+ || left.index - right.index
78
+ }
79
+
80
+ function getFirstUnorderedPair(items) {
81
+ for (let index = 1; index < items.length; index++) {
82
+ const previousItem = items[index - 1]
83
+ const item = items[index]
84
+
85
+ if (compareItems(previousItem, item) > 0) {
86
+ return [
87
+ previousItem,
88
+ item
89
+ ]
90
+ }
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ function getLinebreak(text) {
97
+ return text.includes('\r\n') ? '\r\n' : '\n'
98
+ }
99
+
100
+ function getLineIndent(text, index) {
101
+ const lineStart = text.lastIndexOf('\n', index - 1) + 1
102
+ const indentMatch = /^[ \t]*/.exec(text.slice(lineStart, index))
103
+
104
+ return indentMatch[0]
105
+ }
106
+
107
+ function getBracesRange(node, items, sourceCode) {
108
+ const text = sourceCode.text
109
+ const firstSpecifier = items[0].specifier
110
+ const lastSpecifier = items.at(-1).specifier
111
+ const openingBrace = text.lastIndexOf('{', firstSpecifier.range[0])
112
+ const closingBrace = text.indexOf('}', lastSpecifier.range[1])
113
+ const searchEnd = node.source?.range?.[0] ?? node.range[1]
114
+
115
+ if (
116
+ openingBrace < node.range[0]
117
+ || closingBrace === -1
118
+ || closingBrace > searchEnd
119
+ ) {
120
+ return null
121
+ }
122
+
123
+ return [
124
+ openingBrace + 1,
125
+ closingBrace
126
+ ]
127
+ }
128
+
129
+ function hasInnerComments(sourceCode, range) {
130
+ return sourceCode.getAllComments().some(comment => comment.range[0] > range[0]
131
+ && comment.range[1] < range[1])
132
+ }
133
+
134
+ function shouldBeMultiline(sourceCode, range, items) {
135
+ return items.length > 1
136
+ && !sourceCode.text.slice(range[0], range[1]).includes('\n')
137
+ }
138
+
139
+ function getFixedText(node, items, options, sourceCode, range) {
140
+ const text = sourceCode.text
141
+ const content = text.slice(range[0], range[1])
142
+ const sortedSpecifiers = [...items]
143
+ .sort(compareItems)
144
+ .map(({ specifier }) => sourceCode.getText(specifier))
145
+
146
+ if (!content.includes('\n') && items.length < 2) {
147
+ return ` ${sortedSpecifiers.join(', ')} `
148
+ }
149
+
150
+ const linebreak = getLinebreak(text)
151
+ const firstSpecifier = items[0].specifier
152
+ const closingIndent = getLineIndent(text, node.range[0])
153
+ const indent = content.includes('\n')
154
+ ? getLineIndent(text, firstSpecifier.range[0])
155
+ : `${closingIndent} `
156
+
157
+ return `${linebreak}${indent}${sortedSpecifiers.join(`,${linebreak}${indent}`)}${linebreak}${closingIndent}`
158
+ }
159
+
160
+ function getMessage(left, right) {
161
+ if (left.rank.typeRank !== right.rank.typeRank) {
162
+ return `Expected ${right.rank.name} to come before ${left.rank.name} because of export kind.`
163
+ }
164
+
165
+ if (left.rank.patternRank !== right.rank.patternRank) {
166
+ return `Expected ${right.rank.name} to come before ${left.rank.name} because of naming pattern.`
167
+ }
168
+
169
+ return `Expected ${right.rank.name} to come before ${left.rank.name}.`
170
+ }
171
+
172
+ export default {
173
+ meta: {
174
+ type: 'layout',
175
+ fixable: 'code',
176
+ docs: {
177
+ description: 'Enforce named export specifier order.'
178
+ },
179
+ schema: [
180
+ {
181
+ type: 'object',
182
+ properties: {
183
+ typeExports: {
184
+ enum: [
185
+ 'first',
186
+ 'last',
187
+ 'none'
188
+ ]
189
+ },
190
+ patterns: {
191
+ type: 'array',
192
+ items: {
193
+ type: 'string'
194
+ }
195
+ }
196
+ },
197
+ additionalProperties: true
198
+ }
199
+ ]
200
+ },
201
+ create(context) {
202
+ const options = getOptions(context)
203
+
204
+ return {
205
+ ExportNamedDeclaration(node) {
206
+ const items = getItems(node, options)
207
+
208
+ if (items.length < 2) {
209
+ return
210
+ }
211
+
212
+ const unorderedPair = getFirstUnorderedPair(items)
213
+ const sourceCode = context.sourceCode
214
+ const range = getBracesRange(node, items, sourceCode)
215
+ const invalidMultiline = range && shouldBeMultiline(
216
+ sourceCode,
217
+ range,
218
+ items
219
+ )
220
+
221
+ if (!unorderedPair && !invalidMultiline) {
222
+ return
223
+ }
224
+
225
+ const fix = range && !hasInnerComments(sourceCode, range)
226
+ ? fixer => fixer.replaceTextRange(
227
+ range,
228
+ getFixedText(node, items, options, sourceCode, range)
229
+ )
230
+ : null
231
+ const [
232
+ previousItem,
233
+ item
234
+ ] = unorderedPair ?? [
235
+ items[0],
236
+ items[1]
237
+ ]
238
+
239
+ context.report({
240
+ node: item.specifier,
241
+ message: unorderedPair
242
+ ? getMessage(previousItem, item)
243
+ : 'Expected named exports to be multiline.',
244
+ fix
245
+ })
246
+ }
247
+ }
248
+ }
249
+ }
@@ -310,6 +310,12 @@ export default {
310
310
  },
311
311
  Property(node) {
312
312
  checkProperty(node, 'objectLiteralProperty')
313
+ },
314
+ TSPropertySignature(node) {
315
+ checkProperty(node, 'objectLiteralProperty')
316
+ },
317
+ TSMethodSignature(node) {
318
+ checkProperty(node, 'objectLiteralMethod')
313
319
  }
314
320
  }
315
321
  }
@@ -78,6 +78,17 @@ export default {
78
78
  '^[a-z][a-zA-Z0-9]*$'
79
79
  ]
80
80
  }
81
+ ],
82
+ 'trigen/named-export-order': [
83
+ 'error',
84
+ {
85
+ typeExports: 'first',
86
+ patterns: [
87
+ '^[A-Z][A-Z0-9_]*$',
88
+ '^[A-Z][a-zA-Z0-9]*$',
89
+ '^[a-z][a-zA-Z0-9]*$'
90
+ ]
91
+ }
81
92
  ]
82
93
  }
83
94
  }
package/src/test.js CHANGED
@@ -23,6 +23,7 @@ export default {
23
23
  'typescript/no-unsafe-call': 'off',
24
24
  'typescript/no-unsafe-argument': 'off',
25
25
  'typescript/no-explicit-any': 'off',
26
+ 'typescript/await-thenable': 'off',
26
27
  'typescript/no-floating-promises': 'off',
27
28
  'trigen/import-order': 'off',
28
29
  'eslint/prefer-destructuring': 'off',