@trigen/oxlint-config 9.0.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.
@@ -0,0 +1,191 @@
1
+ const defaultOrder = [
2
+ 'public-static-method',
3
+ 'protected-static-method',
4
+ 'private-static-method',
5
+ 'public-static-field',
6
+ 'protected-static-field',
7
+ 'private-static-field',
8
+ 'public-decorated-field',
9
+ 'protected-decorated-field',
10
+ 'private-decorated-field',
11
+ 'public-instance-field',
12
+ 'protected-instance-field',
13
+ 'private-instance-field',
14
+ 'public-abstract-field',
15
+ 'protected-abstract-field',
16
+ 'signature',
17
+ 'public-constructor',
18
+ 'protected-constructor',
19
+ 'private-constructor',
20
+ 'instance-method'
21
+ ]
22
+
23
+ function getDefaultOptions(context) {
24
+ return context.options[0]?.default ?? {}
25
+ }
26
+
27
+ function getMemberTypes(context) {
28
+ return getDefaultOptions(context).memberTypes ?? defaultOrder
29
+ }
30
+
31
+ function getAccessibility(member) {
32
+ return member.accessibility ?? 'public'
33
+ }
34
+
35
+ function hasDecorators(member) {
36
+ return (member.decorators?.length ?? 0) > 0
37
+ }
38
+
39
+ function isAbstract(member) {
40
+ return member.abstract === true
41
+ }
42
+
43
+ function isSignature(member) {
44
+ return member.type.includes('Signature')
45
+ }
46
+
47
+ function isMethod(member) {
48
+ return member.type === 'MethodDefinition'
49
+ || member.type === 'TSAbstractMethodDefinition'
50
+ }
51
+
52
+ function isField(member) {
53
+ return member.type === 'PropertyDefinition'
54
+ || member.type === 'AccessorProperty'
55
+ || member.type === 'TSAbstractPropertyDefinition'
56
+ }
57
+
58
+ function getMemberType(member) {
59
+ const accessibility = getAccessibility(member)
60
+
61
+ if (isSignature(member)) {
62
+ return 'signature'
63
+ }
64
+
65
+ if (member.kind === 'constructor') {
66
+ return `${accessibility}-constructor`
67
+ }
68
+
69
+ if (isMethod(member)) {
70
+ if (member.static) {
71
+ return `${accessibility}-static-method`
72
+ }
73
+
74
+ return 'instance-method'
75
+ }
76
+
77
+ if (isField(member)) {
78
+ if (isAbstract(member)) {
79
+ return `${accessibility}-abstract-field`
80
+ }
81
+
82
+ if (hasDecorators(member)) {
83
+ return `${accessibility}-decorated-field`
84
+ }
85
+
86
+ if (member.static) {
87
+ return `${accessibility}-static-field`
88
+ }
89
+
90
+ return `${accessibility}-instance-field`
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ function getRank(memberType, memberTypes) {
97
+ const rank = memberTypes.indexOf(memberType)
98
+
99
+ return rank === -1 ? Number.POSITIVE_INFINITY : rank
100
+ }
101
+
102
+ function getMemberName(member) {
103
+ if (member.kind === 'constructor') {
104
+ return 'constructor'
105
+ }
106
+
107
+ const key = member.key
108
+
109
+ if (!key) {
110
+ return member.type
111
+ }
112
+
113
+ if (key.type === 'Identifier' || key.type === 'PrivateIdentifier') {
114
+ return key.name
115
+ }
116
+
117
+ if (
118
+ key.type === 'Literal'
119
+ || key.type === 'StringLiteral'
120
+ || key.type === 'NumericLiteral'
121
+ ) {
122
+ return String(key.value)
123
+ }
124
+
125
+ return member.type
126
+ }
127
+
128
+ export default {
129
+ meta: {
130
+ type: 'suggestion',
131
+ docs: {
132
+ description: 'Enforce configured class member ordering.'
133
+ },
134
+ schema: [
135
+ {
136
+ type: 'object',
137
+ properties: {
138
+ default: {
139
+ type: 'object',
140
+ properties: {
141
+ order: {
142
+ enum: ['as-written']
143
+ },
144
+ memberTypes: {
145
+ type: 'array',
146
+ items: {
147
+ type: 'string'
148
+ }
149
+ }
150
+ },
151
+ additionalProperties: true
152
+ }
153
+ },
154
+ additionalProperties: true
155
+ }
156
+ ]
157
+ },
158
+ create(context) {
159
+ const memberTypes = getMemberTypes(context)
160
+
161
+ return {
162
+ ClassBody(node) {
163
+ let previousMember = null
164
+
165
+ for (const member of node.body) {
166
+ const memberType = getMemberType(member)
167
+
168
+ if (memberType === null) {
169
+ continue
170
+ }
171
+
172
+ const rank = getRank(memberType, memberTypes)
173
+
174
+ if (previousMember && previousMember.rank > rank) {
175
+ context.report({
176
+ node: member,
177
+ message: `Member "${getMemberName(member)}" should be declared before "${getMemberName(previousMember.node)}".`
178
+ })
179
+
180
+ return
181
+ }
182
+
183
+ previousMember = {
184
+ node: member,
185
+ rank
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,229 @@
1
+ const defaultOptions = {
2
+ typeImports: '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 imported = specifier.imported ?? specifier.local
16
+
17
+ if (imported.type === 'Identifier') {
18
+ return imported.name
19
+ }
20
+
21
+ return String(imported.value)
22
+ }
23
+
24
+ function isTypeSpecifier(node, specifier) {
25
+ return node.importKind === 'type'
26
+ || specifier.importKind === 'type'
27
+ }
28
+
29
+ function getTypeRank(node, specifier, options) {
30
+ if (options.typeImports === 'first') {
31
+ return isTypeSpecifier(node, specifier) ? 0 : 1
32
+ }
33
+
34
+ if (options.typeImports === 'last') {
35
+ return isTypeSpecifier(node, specifier) ? 1 : 0
36
+ }
37
+
38
+ return 0
39
+ }
40
+
41
+ function getPatternRank(name, patterns) {
42
+ const rank = patterns.findIndex(pattern => new RegExp(pattern).test(name))
43
+
44
+ return rank === -1 ? Number.POSITIVE_INFINITY : rank
45
+ }
46
+
47
+ function getRank(node, specifier, options) {
48
+ const name = getName(specifier)
49
+
50
+ return {
51
+ name,
52
+ typeRank: getTypeRank(node, specifier, options),
53
+ patternRank: getPatternRank(name, options.patterns)
54
+ }
55
+ }
56
+
57
+ function compareRanks(left, right) {
58
+ return left.typeRank - right.typeRank
59
+ || left.patternRank - right.patternRank
60
+ }
61
+
62
+ function getItems(node, options) {
63
+ return node.specifiers
64
+ .filter(specifier => specifier.type === 'ImportSpecifier')
65
+ .map((specifier, index) => ({
66
+ index,
67
+ specifier,
68
+ rank: getRank(node, specifier, options)
69
+ }))
70
+ }
71
+
72
+ function compareItems(left, right) {
73
+ return compareRanks(left.rank, right.rank)
74
+ || left.index - right.index
75
+ }
76
+
77
+ function getFirstUnorderedPair(items) {
78
+ for (let index = 1; index < items.length; index++) {
79
+ const previousItem = items[index - 1]
80
+ const item = items[index]
81
+
82
+ if (compareItems(previousItem, item) > 0) {
83
+ return [
84
+ previousItem,
85
+ item
86
+ ]
87
+ }
88
+ }
89
+
90
+ return null
91
+ }
92
+
93
+ function getLinebreak(text) {
94
+ return text.includes('\r\n') ? '\r\n' : '\n'
95
+ }
96
+
97
+ function getLineIndent(text, index) {
98
+ const lineStart = text.lastIndexOf('\n', index - 1) + 1
99
+ const indentMatch = /^[ \t]*/.exec(text.slice(lineStart, index))
100
+
101
+ return indentMatch[0]
102
+ }
103
+
104
+ function getBracesRange(node, items, sourceCode) {
105
+ const text = sourceCode.text
106
+ const firstSpecifier = items[0].specifier
107
+ const lastSpecifier = items.at(-1).specifier
108
+ const openingBrace = text.lastIndexOf('{', firstSpecifier.range[0])
109
+ const closingBrace = text.indexOf('}', lastSpecifier.range[1])
110
+ const searchEnd = node.source?.range?.[0] ?? node.range[1]
111
+
112
+ if (
113
+ openingBrace < node.range[0]
114
+ || closingBrace === -1
115
+ || closingBrace > searchEnd
116
+ ) {
117
+ return null
118
+ }
119
+
120
+ return [
121
+ openingBrace + 1,
122
+ closingBrace
123
+ ]
124
+ }
125
+
126
+ function hasInnerComments(sourceCode, range) {
127
+ return sourceCode.getAllComments().some(comment => comment.range[0] > range[0]
128
+ && comment.range[1] < range[1])
129
+ }
130
+
131
+ function getFixedText(node, items, options, sourceCode, range) {
132
+ const text = sourceCode.text
133
+ const content = text.slice(range[0], range[1])
134
+ const sortedSpecifiers = [...items]
135
+ .sort(compareItems)
136
+ .map(({ specifier }) => sourceCode.getText(specifier))
137
+
138
+ if (!content.includes('\n')) {
139
+ return ` ${sortedSpecifiers.join(', ')} `
140
+ }
141
+
142
+ const linebreak = getLinebreak(text)
143
+ const firstSpecifier = items[0].specifier
144
+ const indent = getLineIndent(text, firstSpecifier.range[0])
145
+ const closingIndent = getLineIndent(text, node.range[0])
146
+
147
+ return `${linebreak}${indent}${sortedSpecifiers.join(`,${linebreak}${indent}`)}${linebreak}${closingIndent}`
148
+ }
149
+
150
+ function getMessage(left, right) {
151
+ if (left.rank.typeRank !== right.rank.typeRank) {
152
+ return `Expected ${right.rank.name} to come before ${left.rank.name} because of import kind.`
153
+ }
154
+
155
+ if (left.rank.patternRank !== right.rank.patternRank) {
156
+ return `Expected ${right.rank.name} to come before ${left.rank.name} because of naming pattern.`
157
+ }
158
+
159
+ return `Expected ${right.rank.name} to come before ${left.rank.name}.`
160
+ }
161
+
162
+ export default {
163
+ meta: {
164
+ type: 'layout',
165
+ fixable: 'code',
166
+ docs: {
167
+ description: 'Enforce named import specifier order.'
168
+ },
169
+ schema: [
170
+ {
171
+ type: 'object',
172
+ properties: {
173
+ typeImports: {
174
+ enum: [
175
+ 'first',
176
+ 'last',
177
+ 'none'
178
+ ]
179
+ },
180
+ patterns: {
181
+ type: 'array',
182
+ items: {
183
+ type: 'string'
184
+ }
185
+ }
186
+ },
187
+ additionalProperties: true
188
+ }
189
+ ]
190
+ },
191
+ create(context) {
192
+ const options = getOptions(context)
193
+
194
+ return {
195
+ ImportDeclaration(node) {
196
+ const items = getItems(node, options)
197
+
198
+ if (items.length < 2) {
199
+ return
200
+ }
201
+
202
+ const unorderedPair = getFirstUnorderedPair(items)
203
+
204
+ if (!unorderedPair) {
205
+ return
206
+ }
207
+
208
+ const sourceCode = context.sourceCode
209
+ const range = getBracesRange(node, items, sourceCode)
210
+ const fix = range && !hasInnerComments(sourceCode, range)
211
+ ? fixer => fixer.replaceTextRange(
212
+ range,
213
+ getFixedText(node, items, options, sourceCode, range)
214
+ )
215
+ : null
216
+ const [
217
+ previousItem,
218
+ item
219
+ ] = unorderedPair
220
+
221
+ context.report({
222
+ node: item.specifier,
223
+ message: getMessage(previousItem, item),
224
+ fix
225
+ })
226
+ }
227
+ }
228
+ }
229
+ }
@@ -0,0 +1,300 @@
1
+ const formatPatterns = {
2
+ camelCase: /^[a-z][a-zA-Z0-9]*$/,
3
+ PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
4
+ UPPER_CASE: /^[A-Z][A-Z0-9_]*$/
5
+ }
6
+
7
+ function toArray(value) {
8
+ return Array.isArray(value) ? value : [value]
9
+ }
10
+
11
+ function getSelectorOptions(options, selector, modifiers = []) {
12
+ return options
13
+ .filter((option) => {
14
+ const selectors = toArray(option.selector)
15
+ const optionModifiers = option.modifiers ?? []
16
+
17
+ return (
18
+ selectors.includes(selector)
19
+ || selectors.includes('default')
20
+ )
21
+ && optionModifiers.every(modifier => modifiers.includes(modifier))
22
+ })
23
+ .sort((left, right) => {
24
+ const leftSelectors = toArray(left.selector)
25
+ const rightSelectors = toArray(right.selector)
26
+ const leftScore = leftSelectors.includes(selector) ? 1 : 0
27
+ const rightScore = rightSelectors.includes(selector) ? 1 : 0
28
+
29
+ return leftScore - rightScore
30
+ || (left.modifiers?.length ?? 0) - (right.modifiers?.length ?? 0)
31
+ })
32
+ .at(-1)
33
+ }
34
+
35
+ function getNormalizedName(name, option) {
36
+ if (option.leadingUnderscore === 'allow') {
37
+ return name.replace(/^_+/, '')
38
+ }
39
+
40
+ return name
41
+ }
42
+
43
+ function matchesFormat(name, format) {
44
+ return formatPatterns[format]?.test(name) ?? true
45
+ }
46
+
47
+ function getExpectedFormats(format) {
48
+ return format.join(', ')
49
+ }
50
+
51
+ function isValidName(name, option) {
52
+ if (option?.format === null) {
53
+ return true
54
+ }
55
+
56
+ if (!option?.format) {
57
+ return true
58
+ }
59
+
60
+ const normalizedName = getNormalizedName(name, option)
61
+
62
+ return normalizedName === ''
63
+ || option.format.some(format => matchesFormat(normalizedName, format))
64
+ }
65
+
66
+ function getKeyName(key) {
67
+ if (!key) {
68
+ return null
69
+ }
70
+
71
+ if (key.type === 'Identifier' || key.type === 'PrivateIdentifier') {
72
+ return key.name
73
+ }
74
+
75
+ if (
76
+ key.type === 'Literal'
77
+ || key.type === 'StringLiteral'
78
+ || key.type === 'NumericLiteral'
79
+ ) {
80
+ return String(key.value)
81
+ }
82
+
83
+ return null
84
+ }
85
+
86
+ function isIdentifierName(name) {
87
+ return /^[A-Za-z_$][\w$]*$/.test(name)
88
+ }
89
+
90
+ function requiresQuotes(node) {
91
+ if (node.computed) {
92
+ return false
93
+ }
94
+
95
+ const name = getKeyName(node.key)
96
+
97
+ return name !== null && !isIdentifierName(name)
98
+ }
99
+
100
+ function getPropertyModifiers(node) {
101
+ const modifiers = []
102
+
103
+ if (node.static) {
104
+ modifiers.push('static')
105
+ }
106
+
107
+ if (requiresQuotes(node)) {
108
+ modifiers.push('requiresQuotes')
109
+ }
110
+
111
+ return modifiers
112
+ }
113
+
114
+ function getIdentifierNames(pattern) {
115
+ if (!pattern) {
116
+ return []
117
+ }
118
+
119
+ if (pattern.type === 'Identifier') {
120
+ return [
121
+ {
122
+ name: pattern.name,
123
+ node: pattern
124
+ }
125
+ ]
126
+ }
127
+
128
+ if (pattern.type === 'RestElement') {
129
+ return getIdentifierNames(pattern.argument)
130
+ }
131
+
132
+ if (pattern.type === 'AssignmentPattern') {
133
+ return getIdentifierNames(pattern.left)
134
+ }
135
+
136
+ if (pattern.type === 'ArrayPattern') {
137
+ return pattern.elements.flatMap(getIdentifierNames)
138
+ }
139
+
140
+ if (pattern.type === 'ObjectPattern') {
141
+ return pattern.properties.flatMap(property => (
142
+ property.type === 'Property'
143
+ ? getIdentifierNames(property.value)
144
+ : getIdentifierNames(property.argument)
145
+ ))
146
+ }
147
+
148
+ return []
149
+ }
150
+
151
+ function getParameterNode(parameter) {
152
+ return parameter.type === 'TSParameterProperty'
153
+ ? parameter.parameter
154
+ : parameter
155
+ }
156
+
157
+ export default {
158
+ meta: {
159
+ type: 'suggestion',
160
+ docs: {
161
+ description: 'Enforce configured naming conventions.'
162
+ },
163
+ schema: {
164
+ type: 'array',
165
+ items: {
166
+ type: 'object',
167
+ properties: {
168
+ selector: {
169
+ anyOf: [
170
+ {
171
+ type: 'string'
172
+ },
173
+ {
174
+ type: 'array',
175
+ items: {
176
+ type: 'string'
177
+ }
178
+ }
179
+ ]
180
+ },
181
+ format: {
182
+ anyOf: [
183
+ {
184
+ type: 'array',
185
+ items: {
186
+ type: 'string'
187
+ }
188
+ },
189
+ {
190
+ type: 'null'
191
+ }
192
+ ]
193
+ },
194
+ leadingUnderscore: {
195
+ type: 'string'
196
+ },
197
+ modifiers: {
198
+ type: 'array',
199
+ items: {
200
+ type: 'string'
201
+ }
202
+ }
203
+ },
204
+ required: ['selector'],
205
+ additionalProperties: true
206
+ }
207
+ }
208
+ },
209
+ create(context) {
210
+ const options = context.options
211
+
212
+ function check(name, node, selector, modifiers = []) {
213
+ const option = getSelectorOptions(options, selector, modifiers)
214
+
215
+ if (!option || isValidName(name, option)) {
216
+ return
217
+ }
218
+
219
+ context.report({
220
+ node,
221
+ message: `Name "${name}" must match one of these formats: ${getExpectedFormats(option.format)}.`
222
+ })
223
+ }
224
+
225
+ function checkPattern(pattern, selector) {
226
+ for (const item of getIdentifierNames(pattern)) {
227
+ check(item.name, item.node, selector)
228
+ }
229
+ }
230
+
231
+ function checkProperty(node, selector) {
232
+ const name = getKeyName(node.key)
233
+
234
+ if (name === null) {
235
+ return
236
+ }
237
+
238
+ check(name, node.key, selector, getPropertyModifiers(node))
239
+ }
240
+
241
+ return {
242
+ VariableDeclarator(node) {
243
+ checkPattern(node.id, 'variable')
244
+ },
245
+ FunctionDeclaration(node) {
246
+ if (node.id) {
247
+ check(node.id.name, node.id, 'function')
248
+ }
249
+
250
+ for (const parameter of node.params) {
251
+ checkPattern(getParameterNode(parameter), 'parameter')
252
+ }
253
+ },
254
+ FunctionExpression(node) {
255
+ if (node.id) {
256
+ check(node.id.name, node.id, 'function')
257
+ }
258
+
259
+ for (const parameter of node.params) {
260
+ checkPattern(getParameterNode(parameter), 'parameter')
261
+ }
262
+ },
263
+ ArrowFunctionExpression(node) {
264
+ for (const parameter of node.params) {
265
+ checkPattern(getParameterNode(parameter), 'parameter')
266
+ }
267
+ },
268
+ ClassDeclaration(node) {
269
+ if (node.id) {
270
+ check(node.id.name, node.id, 'typeLike')
271
+ }
272
+ },
273
+ TSTypeAliasDeclaration(node) {
274
+ check(node.id.name, node.id, 'typeLike')
275
+ },
276
+ TSInterfaceDeclaration(node) {
277
+ check(node.id.name, node.id, 'interface')
278
+ },
279
+ TSEnumDeclaration(node) {
280
+ check(node.id.name, node.id, 'typeLike')
281
+ },
282
+ TSEnumMember(node) {
283
+ const name = getKeyName(node.id)
284
+
285
+ if (name !== null) {
286
+ check(name, node.id, 'enumMember')
287
+ }
288
+ },
289
+ PropertyDefinition(node) {
290
+ checkProperty(node, 'classProperty')
291
+ },
292
+ MethodDefinition(node) {
293
+ checkProperty(node, 'classProperty')
294
+ },
295
+ Property(node) {
296
+ checkProperty(node, 'objectLiteralProperty')
297
+ }
298
+ }
299
+ }
300
+ }