@diia-inhouse/oxc-config 1.9.2
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/LICENCE.md +287 -0
- package/README.md +93 -0
- package/bin/dev.mjs +18 -0
- package/dist/oxfmt.config.d.ts +24 -0
- package/dist/oxfmt.config.d.ts.map +1 -0
- package/dist/oxfmt.config.js +85 -0
- package/dist/oxlint/config.d.ts +8 -0
- package/dist/oxlint/config.d.ts.map +1 -0
- package/dist/oxlint/config.js +451 -0
- package/dist/oxlint/plugins/classEncapsulation.mjs +108 -0
- package/dist/oxlint/plugins/codeConventions.mjs +278 -0
- package/dist/oxlint/plugins/mongooseSchema.mjs +183 -0
- package/dist/oxlint/plugins/noHardcodedCyrillic.mjs +43 -0
- package/dist/oxlint/plugins/packageCheck.mjs +84 -0
- package/dist/oxlint/plugins/temporalConventions.mjs +169 -0
- package/dist/oxlint/plugins/testConventions.mjs +67 -0
- package/oxlint/plugins/classEncapsulation.mjs +108 -0
- package/oxlint/plugins/codeConventions.mjs +278 -0
- package/oxlint/plugins/mongooseSchema.mjs +183 -0
- package/oxlint/plugins/noHardcodedCyrillic.mjs +43 -0
- package/oxlint/plugins/packageCheck.mjs +84 -0
- package/oxlint/plugins/temporalConventions.mjs +169 -0
- package/oxlint/plugins/testConventions.mjs +67 -0
- package/package.json +68 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
function isSchemaConstructor(callee) {
|
|
2
|
+
return (
|
|
3
|
+
(callee.type === 'Identifier' && callee.name === 'Schema') ||
|
|
4
|
+
(callee.type === 'MemberExpression' &&
|
|
5
|
+
callee.object.type === 'Identifier' &&
|
|
6
|
+
callee.object.name === 'mongoose' &&
|
|
7
|
+
callee.property.type === 'Identifier' &&
|
|
8
|
+
callee.property.name === 'Schema')
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getOptionValue(optionsNode, key) {
|
|
13
|
+
if (!optionsNode || optionsNode.type !== 'ObjectExpression') {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const prop = optionsNode.properties.find((p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === key)
|
|
18
|
+
|
|
19
|
+
if (!prop) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return prop.value.type === 'Literal' ? prop.value.value : undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isModelFile(filename) {
|
|
27
|
+
return filename.includes('/models/')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
meta: { name: '@diia-inhouse/oxlint-plugin-mongoose' },
|
|
32
|
+
rules: {
|
|
33
|
+
'schema-timestamps': {
|
|
34
|
+
meta: {
|
|
35
|
+
type: 'problem',
|
|
36
|
+
messages: {
|
|
37
|
+
missing: 'Mongoose schema is missing { timestamps: true } in options.',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
create(context) {
|
|
41
|
+
if (!isModelFile(context.filename)) {
|
|
42
|
+
return {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const subSchemaVars = new Set()
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
'VariableDeclarator[init.type="NewExpression"]'(node) {
|
|
49
|
+
if (!isSchemaConstructor(node.init.callee)) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const options = node.init.arguments[1]
|
|
54
|
+
|
|
55
|
+
if (getOptionValue(options, '_id') === false) {
|
|
56
|
+
subSchemaVars.add(node.id.name)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
'ExportDefaultDeclaration, ExportNamedDeclaration'() {
|
|
60
|
+
// Reset tracking per export boundary — not needed
|
|
61
|
+
},
|
|
62
|
+
'Program:exit'(programNode) {
|
|
63
|
+
for (const stmt of programNode.body) {
|
|
64
|
+
if (stmt.type !== 'VariableDeclaration') {
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const decl of stmt.declarations) {
|
|
69
|
+
if (!decl.init || decl.init.type !== 'NewExpression' || !isSchemaConstructor(decl.init.callee)) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (subSchemaVars.has(decl.id.name)) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const options = decl.init.arguments[1]
|
|
78
|
+
|
|
79
|
+
if (getOptionValue(options, 'timestamps') !== true) {
|
|
80
|
+
context.report({ node: decl, messageId: 'missing' })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
'sub-schema-id-false': {
|
|
90
|
+
meta: {
|
|
91
|
+
type: 'problem',
|
|
92
|
+
messages: {
|
|
93
|
+
missing: 'Sub-schema is missing { _id: false } in options. Embedded documents should not generate _id.',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
create(context) {
|
|
97
|
+
if (!isModelFile(context.filename)) {
|
|
98
|
+
return {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const schemaVars = new Map()
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
VariableDeclarator(node) {
|
|
105
|
+
if (!node.init || node.init.type !== 'NewExpression' || !isSchemaConstructor(node.init.callee)) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
schemaVars.set(node.id.name, {
|
|
110
|
+
node: node.init,
|
|
111
|
+
hasIdFalse: getOptionValue(node.init.arguments[1], '_id') === false,
|
|
112
|
+
hasTimestamps: getOptionValue(node.init.arguments[1], 'timestamps') === true,
|
|
113
|
+
usedAsSubSchema: false,
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
Property(node) {
|
|
117
|
+
if (node.key.type !== 'Identifier' || node.key.name !== 'type' || node.value.type !== 'ArrayExpression') {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const el of node.value.elements) {
|
|
122
|
+
if (el && el.type === 'Identifier' && schemaVars.has(el.name)) {
|
|
123
|
+
schemaVars.get(el.name).usedAsSubSchema = true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
'Program:exit'() {
|
|
128
|
+
for (const [, info] of schemaVars) {
|
|
129
|
+
if (info.usedAsSubSchema && !info.hasIdFalse) {
|
|
130
|
+
context.report({ node: info.node, messageId: 'missing' })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
'status-requires-history': {
|
|
139
|
+
meta: {
|
|
140
|
+
type: 'problem',
|
|
141
|
+
messages: {
|
|
142
|
+
missing:
|
|
143
|
+
'Schema has "status" field but no "statusHistory". Every model with status MUST have statusHistory — missing it is a data corruption bug.',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
create(context) {
|
|
147
|
+
if (!isModelFile(context.filename)) {
|
|
148
|
+
return {}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
NewExpression(node) {
|
|
153
|
+
if (!isSchemaConstructor(node.callee)) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const options = node.arguments[1]
|
|
158
|
+
|
|
159
|
+
if (getOptionValue(options, '_id') === false) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const fields = node.arguments[0]
|
|
164
|
+
|
|
165
|
+
if (!fields || fields.type !== 'ObjectExpression') {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const fieldNames = new Set(
|
|
170
|
+
fields.properties.filter((p) => p.type === 'Property' && p.key.type === 'Identifier').map((p) => p.key.name),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const hasHistory = [...fieldNames].some((name) => name.toLowerCase().includes('statushistor'))
|
|
174
|
+
|
|
175
|
+
if (fieldNames.has('status') && !hasHistory) {
|
|
176
|
+
context.report({ node, messageId: 'missing' })
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const CYRILLIC_PATTERN = /[\u0400-\u04FF\u0500-\u052F]/
|
|
2
|
+
|
|
3
|
+
const EXCLUDED_PATHS = ['/locales/', '.spec.ts', '.test.ts', '/tests/']
|
|
4
|
+
|
|
5
|
+
function isExcludedFile(filename) {
|
|
6
|
+
return EXCLUDED_PATHS.some((path) => filename.includes(path))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function checkStringNode(context, node) {
|
|
10
|
+
if (isExcludedFile(context.filename)) {
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const value = node.type === 'TemplateLiteral' ? node.quasis.map((q) => q.value.raw).join('') : node.value
|
|
15
|
+
|
|
16
|
+
if (typeof value === 'string' && CYRILLIC_PATTERN.test(value)) {
|
|
17
|
+
context.report({ node, messageId: 'forbidden', data: { text: value.length > 50 ? value.slice(0, 50) + '...' : value } })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
meta: { name: '@diia-inhouse/oxlint-plugin-locale' },
|
|
23
|
+
rules: {
|
|
24
|
+
'no-hardcoded-cyrillic': {
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
messages: {
|
|
28
|
+
forbidden: 'Hardcoded Cyrillic string "{{ text }}" found. Move user-facing text to locale files and use i18n service.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
return {
|
|
33
|
+
Literal(node) {
|
|
34
|
+
checkStringNode(context, node)
|
|
35
|
+
},
|
|
36
|
+
TemplateLiteral(node) {
|
|
37
|
+
checkStringNode(context, node)
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
let pkg = null
|
|
5
|
+
let loaded = false
|
|
6
|
+
|
|
7
|
+
function loadPackageJson() {
|
|
8
|
+
if (loaded) {
|
|
9
|
+
return pkg
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
loaded = true
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
pkg = JSON.parse(readFileSync(resolve('package.json'), 'utf8'))
|
|
16
|
+
} catch {
|
|
17
|
+
// package.json not found or not parseable
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return pkg
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const VERSION_RANGE_PATTERN = /^[\^~]/
|
|
24
|
+
|
|
25
|
+
const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
meta: { name: '@diia-inhouse/oxlint-plugin-package' },
|
|
29
|
+
rules: {
|
|
30
|
+
'no-service-in-package-name': {
|
|
31
|
+
meta: {
|
|
32
|
+
type: 'problem',
|
|
33
|
+
messages: {
|
|
34
|
+
forbidden: 'package.json "name" field must not contain the word "service" (found: "{{ name }}")',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
return {
|
|
39
|
+
Program(node) {
|
|
40
|
+
const data = loadPackageJson()
|
|
41
|
+
|
|
42
|
+
if (data?.name?.includes('service')) {
|
|
43
|
+
context.report({ node, messageId: 'forbidden', data: { name: data.name } })
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
'pinned-dependencies': {
|
|
51
|
+
meta: {
|
|
52
|
+
type: 'problem',
|
|
53
|
+
messages: {
|
|
54
|
+
unpinned: 'Dependency "{{ name }}" has unpinned version "{{ version }}". Pin to an exact version (remove ^ or ~).',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
create(context) {
|
|
58
|
+
return {
|
|
59
|
+
Program(node) {
|
|
60
|
+
const data = loadPackageJson()
|
|
61
|
+
|
|
62
|
+
if (!data) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const field of DEP_FIELDS) {
|
|
67
|
+
const deps = data[field]
|
|
68
|
+
|
|
69
|
+
if (!deps) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
74
|
+
if (typeof version === 'string' && VERSION_RANGE_PATTERN.test(version)) {
|
|
75
|
+
context.report({ node, messageId: 'unpinned', data: { name, version } })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
function isTestFile(filename) {
|
|
2
|
+
return filename.includes('/tests/') || filename.includes('/test/')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function isWorkflowFile(filename) {
|
|
6
|
+
return !isTestFile(filename) && filename.includes('/worker/workflows/') && !filename.includes('.types.')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isActivityFile(filename) {
|
|
10
|
+
return !isTestFile(filename) && filename.includes('/worker/activities/')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FORBIDDEN_NODE_MODULES = new Set([
|
|
14
|
+
'node:path',
|
|
15
|
+
'path',
|
|
16
|
+
'node:fs',
|
|
17
|
+
'fs',
|
|
18
|
+
'node:fs/promises',
|
|
19
|
+
'fs/promises',
|
|
20
|
+
'node:crypto',
|
|
21
|
+
'crypto',
|
|
22
|
+
'node:child_process',
|
|
23
|
+
'child_process',
|
|
24
|
+
'node:os',
|
|
25
|
+
'os',
|
|
26
|
+
'node:net',
|
|
27
|
+
'net',
|
|
28
|
+
'node:dns',
|
|
29
|
+
'dns',
|
|
30
|
+
'node:http',
|
|
31
|
+
'http',
|
|
32
|
+
'node:https',
|
|
33
|
+
'https',
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
meta: { name: '@diia-inhouse/oxlint-plugin-temporal' },
|
|
38
|
+
rules: {
|
|
39
|
+
'workflow-single-param': {
|
|
40
|
+
meta: {
|
|
41
|
+
type: 'problem',
|
|
42
|
+
messages: {
|
|
43
|
+
tooMany:
|
|
44
|
+
'Workflow function "{{ name }}" has {{ count }} parameters. Workflows must accept a single object parameter for serialization compatibility.',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
create(context) {
|
|
48
|
+
if (!isWorkflowFile(context.filename)) {
|
|
49
|
+
return {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkFunction(node, name) {
|
|
53
|
+
if (node.params.length > 1) {
|
|
54
|
+
context.report({
|
|
55
|
+
node,
|
|
56
|
+
messageId: 'tooMany',
|
|
57
|
+
data: { name: name || '<anonymous>', count: String(node.params.length) },
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
'ExportNamedDeclaration > FunctionDeclaration'(node) {
|
|
64
|
+
checkFunction(node, node.id?.name)
|
|
65
|
+
},
|
|
66
|
+
'ExportNamedDeclaration > VariableDeclaration > VariableDeclarator'(node) {
|
|
67
|
+
if (node.init && (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression')) {
|
|
68
|
+
checkFunction(node.init, node.id?.name)
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
'async-activity': {
|
|
76
|
+
meta: {
|
|
77
|
+
type: 'problem',
|
|
78
|
+
messages: {
|
|
79
|
+
notAsync: 'Activity method "{{ name }}" must be async. All Temporal activities must be async functions.',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
create(context) {
|
|
83
|
+
if (!isActivityFile(context.filename)) {
|
|
84
|
+
return {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
MethodDefinition(node) {
|
|
89
|
+
if (node.kind === 'constructor' || node.key.type !== 'Identifier' || node.accessibility === 'private') {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fn = node.value
|
|
94
|
+
|
|
95
|
+
if (fn && !fn.async) {
|
|
96
|
+
context.report({
|
|
97
|
+
node: node.key,
|
|
98
|
+
messageId: 'notAsync',
|
|
99
|
+
data: { name: node.key.name },
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
'no-node-imports': {
|
|
108
|
+
meta: {
|
|
109
|
+
type: 'problem',
|
|
110
|
+
messages: {
|
|
111
|
+
forbidden:
|
|
112
|
+
'Importing "{{ source }}" in a workflow file will break the Temporal bundle. Workflows run in a sandboxed VM — Node.js built-in modules are not available.',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
create(context) {
|
|
116
|
+
if (!isWorkflowFile(context.filename)) {
|
|
117
|
+
return {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
ImportDeclaration(node) {
|
|
122
|
+
const source = node.source.value
|
|
123
|
+
|
|
124
|
+
if (FORBIDDEN_NODE_MODULES.has(source)) {
|
|
125
|
+
context.report({
|
|
126
|
+
node: node.source,
|
|
127
|
+
messageId: 'forbidden',
|
|
128
|
+
data: { source },
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
'no-path-alias-imports': {
|
|
137
|
+
meta: {
|
|
138
|
+
type: 'problem',
|
|
139
|
+
messages: {
|
|
140
|
+
forbidden:
|
|
141
|
+
'Path alias "{{ source }}" cannot be used in workflow files. The Temporal bundler does not resolve tsconfig path aliases. Use relative imports or import only from type-only workflow packages.',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
create(context) {
|
|
145
|
+
if (!isWorkflowFile(context.filename)) {
|
|
146
|
+
return {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
ImportDeclaration(node) {
|
|
151
|
+
if (node.importKind === 'type') {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const source = node.source.value
|
|
156
|
+
|
|
157
|
+
if (source.startsWith('@') && !source.startsWith('@diia-inhouse/') && !source.startsWith('@temporalio/')) {
|
|
158
|
+
context.report({
|
|
159
|
+
node: node.source,
|
|
160
|
+
messageId: 'forbidden',
|
|
161
|
+
data: { source },
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const PERSISTENT_MOCK_METHODS = new Set(['mockResolvedValue', 'mockReturnValue', 'mockRejectedValue', 'mockImplementation'])
|
|
2
|
+
|
|
3
|
+
function getOnceVariant(name) {
|
|
4
|
+
return `${name}Once`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
meta: { name: '@diia-inhouse/oxlint-plugin-test' },
|
|
9
|
+
rules: {
|
|
10
|
+
'no-persistent-mock': {
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
messages: {
|
|
14
|
+
forbidden:
|
|
15
|
+
'Use {{ once }}() instead of {{ method }}(). Persistent mocks hide test isolation issues and mask unexpected call counts.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
CallExpression(node) {
|
|
21
|
+
if (
|
|
22
|
+
node.callee.type === 'MemberExpression' &&
|
|
23
|
+
node.callee.property.type === 'Identifier' &&
|
|
24
|
+
PERSISTENT_MOCK_METHODS.has(node.callee.property.name)
|
|
25
|
+
) {
|
|
26
|
+
const method = node.callee.property.name
|
|
27
|
+
|
|
28
|
+
context.report({
|
|
29
|
+
node: node.callee.property,
|
|
30
|
+
messageId: 'forbidden',
|
|
31
|
+
data: { method, once: getOnceVariant(method) },
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
'no-vi-mock-in-workflows': {
|
|
40
|
+
meta: {
|
|
41
|
+
type: 'problem',
|
|
42
|
+
messages: {
|
|
43
|
+
forbidden: 'vi.mock() is forbidden in workflow tests. Use mockActivities() from Temporal test utilities instead.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
create(context) {
|
|
47
|
+
if (!context.filename.includes('/worker/')) {
|
|
48
|
+
return {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
CallExpression(node) {
|
|
53
|
+
if (
|
|
54
|
+
node.callee.type === 'MemberExpression' &&
|
|
55
|
+
node.callee.object.type === 'Identifier' &&
|
|
56
|
+
node.callee.object.name === 'vi' &&
|
|
57
|
+
node.callee.property.type === 'Identifier' &&
|
|
58
|
+
node.callee.property.name === 'mock'
|
|
59
|
+
) {
|
|
60
|
+
context.report({ node, messageId: 'forbidden' })
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diia-inhouse/oxc-config",
|
|
3
|
+
"version": "1.9.2",
|
|
4
|
+
"description": "OXC toolchain configs for Diia services: oxlint, oxfmt, oxc transformer",
|
|
5
|
+
"author": "Diia",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"diia-dev": "./bin/dev.mjs"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
"./oxlint": "./dist/oxlint/config.js",
|
|
13
|
+
"./oxfmt": "./dist/oxfmt.config.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"oxlint/plugins",
|
|
18
|
+
"bin"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"build": "tsc -p tsconfig.build.json && cp -r oxlint/plugins dist/oxlint/plugins",
|
|
26
|
+
"prepare": "npm run build",
|
|
27
|
+
"lint": "oxlint && oxfmt --check",
|
|
28
|
+
"lint-fix": "oxlint --fix && oxfmt",
|
|
29
|
+
"find-circulars": "madge --circular --extensions ts ./",
|
|
30
|
+
"lint:lockfile": "lockfile-lint --path package-lock.json --allowed-hosts registry.npmjs.org --validate-https",
|
|
31
|
+
"semantic-release": "semantic-release"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@boundaries/eslint-plugin": "6.0.2",
|
|
35
|
+
"@diia-inhouse/eslint-plugin": "1.9.6",
|
|
36
|
+
"@stylistic/eslint-plugin": "5.10.0",
|
|
37
|
+
"eslint-import-resolver-oxc": "0.15.0",
|
|
38
|
+
"eslint-plugin-regexp": "3.1.0",
|
|
39
|
+
"eslint-plugin-security": "4.0.0",
|
|
40
|
+
"tsx": "4.21.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"oxfmt": ">= 0.43.0",
|
|
44
|
+
"oxlint": ">= 1.58.0",
|
|
45
|
+
"oxlint-tsgolint": ">= 0.20.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@diia-inhouse/configs": "6.1.1",
|
|
49
|
+
"@typescript-eslint/parser": "8.59.1",
|
|
50
|
+
"@typescript-eslint/rule-tester": "8.59.1",
|
|
51
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
52
|
+
"@vitest/ui": "4.1.5",
|
|
53
|
+
"lockfile-lint": "5.0.0",
|
|
54
|
+
"madge": "8.0.0",
|
|
55
|
+
"oxfmt": "0.47.0",
|
|
56
|
+
"oxlint": "1.62.0",
|
|
57
|
+
"oxlint-tsgolint": "0.22.1",
|
|
58
|
+
"semantic-release": "24.2.7",
|
|
59
|
+
"vitest": "4.1.5"
|
|
60
|
+
},
|
|
61
|
+
"commitlint": {
|
|
62
|
+
"extends": "@diia-inhouse/configs/dist/commitlint"
|
|
63
|
+
},
|
|
64
|
+
"release": {
|
|
65
|
+
"extends": "@diia-inhouse/configs/dist/semantic-release/package"
|
|
66
|
+
},
|
|
67
|
+
"repository": "https://github.com/diia-open-source/be-oxc-config"
|
|
68
|
+
}
|