@luxfi/biome-config 1.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.
- package/base.jsonc +1047 -0
- package/package.json +25 -0
- package/project.json +27 -0
- package/scripts/generate.js +81 -0
- package/src/extractor.js +44 -0
- package/src/extractor.test.js +138 -0
- package/src/fixtures/array-merge-config.jsonc +52 -0
- package/src/fixtures/no-markers-config.jsonc +37 -0
- package/src/fixtures/off-override-config.jsonc +41 -0
- package/src/fixtures/simple-config.jsonc +45 -0
- package/src/merger.js +100 -0
- package/src/merger.test.js +178 -0
- package/src/processor.js +121 -0
- package/src/processor.test.js +336 -0
- package/src/universePackages.js +144 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { mergeArrayValues, mergeObjectValues } from './merger.js'
|
|
3
|
+
|
|
4
|
+
describe('mergeObjectValues', () => {
|
|
5
|
+
test('should merge global and local object values', () => {
|
|
6
|
+
const global = {
|
|
7
|
+
lodash: 'Use lodash-es',
|
|
8
|
+
moment: 'Use date-fns',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const local = {
|
|
12
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
13
|
+
react: 'Custom restriction',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = mergeObjectValues(global, local)
|
|
17
|
+
|
|
18
|
+
expect(result).toMatchObject({
|
|
19
|
+
lodash: 'Use lodash-es',
|
|
20
|
+
moment: 'Use date-fns',
|
|
21
|
+
react: 'Custom restriction',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(result.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('should prioritize local values over global', () => {
|
|
28
|
+
const global = {
|
|
29
|
+
lodash: 'Use lodash-es',
|
|
30
|
+
moment: 'Use date-fns',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const local = {
|
|
34
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
35
|
+
lodash: 'Lodash is fine actually',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const result = mergeObjectValues(global, local)
|
|
39
|
+
|
|
40
|
+
expect(result.lodash).toBe('Lodash is fine actually')
|
|
41
|
+
expect(result.moment).toBe('Use date-fns')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should handle "off" overrides', () => {
|
|
45
|
+
const global = {
|
|
46
|
+
lodash: 'Use lodash-es',
|
|
47
|
+
moment: 'Use date-fns',
|
|
48
|
+
jquery: 'No jQuery',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const local = {
|
|
52
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
53
|
+
jquery: 'off',
|
|
54
|
+
react: 'Custom restriction',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = mergeObjectValues(global, local)
|
|
58
|
+
|
|
59
|
+
expect(result.lodash).toBe('Use lodash-es')
|
|
60
|
+
expect(result.moment).toBe('Use date-fns')
|
|
61
|
+
expect(result.jquery).toBeUndefined()
|
|
62
|
+
expect(result.react).toBe('Custom restriction')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('should remove marker from result', () => {
|
|
66
|
+
const global = {
|
|
67
|
+
lodash: 'Use lodash-es',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const local = {
|
|
71
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
72
|
+
react: 'Custom restriction',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = mergeObjectValues(global, local)
|
|
76
|
+
|
|
77
|
+
expect(result.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('should handle empty global object', () => {
|
|
81
|
+
const global = {}
|
|
82
|
+
|
|
83
|
+
const local = {
|
|
84
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
85
|
+
react: 'Custom restriction',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = mergeObjectValues(global, local)
|
|
89
|
+
|
|
90
|
+
expect(result).toMatchObject({
|
|
91
|
+
react: 'Custom restriction',
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('should handle empty local object (except marker)', () => {
|
|
96
|
+
const global = {
|
|
97
|
+
lodash: 'Use lodash-es',
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const local = {
|
|
101
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = mergeObjectValues(global, local)
|
|
105
|
+
|
|
106
|
+
expect(result).toMatchObject({
|
|
107
|
+
lodash: 'Use lodash-es',
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('mergeArrayValues', () => {
|
|
113
|
+
test('should merge global and local arrays', () => {
|
|
114
|
+
const global = ['event', 'name', 'global']
|
|
115
|
+
const local = ['__INCLUDE_GLOBAL_VALUES__', 'localStorage', 'sessionStorage']
|
|
116
|
+
|
|
117
|
+
const result = mergeArrayValues(global, local)
|
|
118
|
+
|
|
119
|
+
expect(result).toEqual(['localStorage', 'sessionStorage', 'event', 'name', 'global'])
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('should deduplicate merged arrays', () => {
|
|
123
|
+
const global = ['event', 'name', 'global']
|
|
124
|
+
const local = ['__INCLUDE_GLOBAL_VALUES__', 'event', 'localStorage']
|
|
125
|
+
|
|
126
|
+
const result = mergeArrayValues(global, local)
|
|
127
|
+
|
|
128
|
+
// Should not have duplicates
|
|
129
|
+
expect(result.filter((x) => x === 'event')).toHaveLength(1)
|
|
130
|
+
|
|
131
|
+
// Should maintain local precedence (local items first)
|
|
132
|
+
expect(result).toEqual(['event', 'localStorage', 'name', 'global'])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('should remove marker from result', () => {
|
|
136
|
+
const global = ['event', 'name']
|
|
137
|
+
const local = ['__INCLUDE_GLOBAL_VALUES__', 'localStorage']
|
|
138
|
+
|
|
139
|
+
const result = mergeArrayValues(global, local)
|
|
140
|
+
|
|
141
|
+
expect(result).not.toContain('__INCLUDE_GLOBAL_VALUES__')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('should handle empty global array', () => {
|
|
145
|
+
const global = []
|
|
146
|
+
const local = ['__INCLUDE_GLOBAL_VALUES__', 'localStorage']
|
|
147
|
+
|
|
148
|
+
const result = mergeArrayValues(global, local)
|
|
149
|
+
|
|
150
|
+
expect(result).toEqual(['localStorage'])
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('should handle local array with only marker', () => {
|
|
154
|
+
const global = ['event', 'name']
|
|
155
|
+
const local = ['__INCLUDE_GLOBAL_VALUES__']
|
|
156
|
+
|
|
157
|
+
const result = mergeArrayValues(global, local)
|
|
158
|
+
|
|
159
|
+
expect(result).toEqual(['event', 'name'])
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('should deduplicate complex objects', () => {
|
|
163
|
+
const global = [{ path: 'lodash', message: 'Use lodash-es' }]
|
|
164
|
+
const local = [
|
|
165
|
+
'__INCLUDE_GLOBAL_VALUES__',
|
|
166
|
+
{ path: 'lodash', message: 'Use lodash-es' },
|
|
167
|
+
{ path: 'moment', message: 'Use date-fns' },
|
|
168
|
+
{ message: 'Use date-fns', path: 'moment' },
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
const result = mergeArrayValues(global, local)
|
|
172
|
+
|
|
173
|
+
// Should deduplicate based on JSON serialization
|
|
174
|
+
expect(result).toHaveLength(2)
|
|
175
|
+
expect(result[0]).toMatchObject({ path: 'lodash', message: 'Use lodash-es' })
|
|
176
|
+
expect(result[1]).toMatchObject({ path: 'moment', message: 'Use date-fns' })
|
|
177
|
+
})
|
|
178
|
+
})
|
package/src/processor.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const { mergeObjectValues, mergeArrayValues } = require('./merger')
|
|
2
|
+
const {
|
|
3
|
+
detectUniversePackages,
|
|
4
|
+
generateUniversePackageOverrides,
|
|
5
|
+
getGlobalRestrictedImportPatterns,
|
|
6
|
+
} = require('./universePackages')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Processes the entire config, resolving markers in all overrides
|
|
10
|
+
* @param {Object} baseConfig - The base configuration with overrides
|
|
11
|
+
* @param {Map<string, any>} globalRules - Map of global rule paths to values
|
|
12
|
+
* @returns {Object} Configuration with markers resolved
|
|
13
|
+
*/
|
|
14
|
+
function processConfig(baseConfig, globalRules) {
|
|
15
|
+
// Deep clone to avoid mutating original
|
|
16
|
+
const config = structuredClone(baseConfig)
|
|
17
|
+
|
|
18
|
+
// Process each override section
|
|
19
|
+
if (Array.isArray(config.overrides)) {
|
|
20
|
+
const processedOverrides = []
|
|
21
|
+
|
|
22
|
+
// First pass: expand markers that generate multiple overrides
|
|
23
|
+
for (const override of config.overrides) {
|
|
24
|
+
if (override === '__AUTO_GENERATE_UNIVERSE_OVERRIDES__') {
|
|
25
|
+
const generatedOverrides = expandUniverseOverridesMarker(config)
|
|
26
|
+
processedOverrides.push(...generatedOverrides)
|
|
27
|
+
} else {
|
|
28
|
+
processedOverrides.push(override)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Second pass: process each override to resolve __INCLUDE_GLOBAL_VALUES__ markers
|
|
33
|
+
config.overrides = processedOverrides.map((override) =>
|
|
34
|
+
override.linter?.rules ? resolveIncludeGlobalValuesMarkers(override, globalRules) : override
|
|
35
|
+
)
|
|
36
|
+
} else if (config.overrides) {
|
|
37
|
+
throw new Error('`overrides` must be an array')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return config
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Expands __AUTO_GENERATE_UNIVERSE_OVERRIDES__ marker into actual override configurations
|
|
45
|
+
* @param {Object} baseConfig - The base configuration (needed to extract global patterns)
|
|
46
|
+
* @returns {Array<Object>} Array of generated override configurations
|
|
47
|
+
*/
|
|
48
|
+
function expandUniverseOverridesMarker(baseConfig) {
|
|
49
|
+
const universePackages = detectUniversePackages()
|
|
50
|
+
const globalPatterns = getGlobalRestrictedImportPatterns(baseConfig)
|
|
51
|
+
const generatedOverrides = generateUniversePackageOverrides(universePackages, globalPatterns)
|
|
52
|
+
|
|
53
|
+
console.log(`✓ Auto-generated ${generatedOverrides.length} override(s) for @luxfi/* packages`)
|
|
54
|
+
|
|
55
|
+
return generatedOverrides
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolves __INCLUDE_GLOBAL_VALUES__ markers in an override's rule options
|
|
60
|
+
* @param {Object} override - Override configuration section
|
|
61
|
+
* @param {Map<string, any>} globalRules - Map of global rule paths to values
|
|
62
|
+
* @returns {Object} Processed override with markers resolved
|
|
63
|
+
*/
|
|
64
|
+
function resolveIncludeGlobalValuesMarkers(override, globalRules) {
|
|
65
|
+
// Deep clone to avoid mutation
|
|
66
|
+
const processed = structuredClone(override)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Recursively walks override rules tree to find and resolve markers
|
|
70
|
+
* @param {Object} obj - Current object being walked
|
|
71
|
+
* @param {Array<string>} pathParts - Path components leading to this object
|
|
72
|
+
*/
|
|
73
|
+
function walkAndMerge(obj, pathParts) {
|
|
74
|
+
if (!obj || typeof obj !== 'object') {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
79
|
+
const currentPath = [...pathParts, key]
|
|
80
|
+
|
|
81
|
+
// Check if this is a rule's options object with potential markers
|
|
82
|
+
if (key === 'options' && value && typeof value === 'object') {
|
|
83
|
+
const rulePath = pathParts.join('.')
|
|
84
|
+
const globalRule = globalRules.get(rulePath)
|
|
85
|
+
|
|
86
|
+
// Process each option key generically
|
|
87
|
+
for (const [optionKey, optionValue] of Object.entries(value)) {
|
|
88
|
+
const globalOptionValue = globalRule?.options?.[optionKey]
|
|
89
|
+
|
|
90
|
+
// Check for marker in object-type options
|
|
91
|
+
const isObjectWithMarker =
|
|
92
|
+
optionValue &&
|
|
93
|
+
typeof optionValue === 'object' &&
|
|
94
|
+
!Array.isArray(optionValue) &&
|
|
95
|
+
optionValue.__INCLUDE_GLOBAL_VALUES__
|
|
96
|
+
|
|
97
|
+
if (isObjectWithMarker) {
|
|
98
|
+
obj[key][optionKey] = mergeObjectValues(globalOptionValue || {}, optionValue)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for marker in array-type options
|
|
102
|
+
const isArrayWithMarker = Array.isArray(optionValue) && optionValue.includes('__INCLUDE_GLOBAL_VALUES__')
|
|
103
|
+
|
|
104
|
+
if (isArrayWithMarker) {
|
|
105
|
+
obj[key][optionKey] = mergeArrayValues(globalOptionValue || [], optionValue)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Continue walking nested objects
|
|
111
|
+
if (typeof value === 'object' && value !== null) {
|
|
112
|
+
walkAndMerge(value, currentPath)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
walkAndMerge(processed.linter.rules, ['linter', 'rules'])
|
|
118
|
+
return processed
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { processConfig }
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { parse as parseJsonc } from 'jsonc-parser'
|
|
5
|
+
import { extractGlobalRuleValues } from './extractor.js'
|
|
6
|
+
import { processConfig } from './processor.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helper to load and process a fixture file
|
|
10
|
+
*/
|
|
11
|
+
function processFixture(fixtureName) {
|
|
12
|
+
const fixturePath = path.join(__dirname, 'fixtures', fixtureName)
|
|
13
|
+
const content = fs.readFileSync(fixturePath, 'utf8')
|
|
14
|
+
const config = parseJsonc(content)
|
|
15
|
+
const globalRules = extractGlobalRuleValues(config)
|
|
16
|
+
return processConfig(config, globalRules)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('Biome Config Processor', () => {
|
|
20
|
+
describe('Object Marker Resolution', () => {
|
|
21
|
+
test('should merge global paths with override paths', () => {
|
|
22
|
+
const result = processFixture('simple-config.jsonc')
|
|
23
|
+
|
|
24
|
+
const override = result.overrides[0]
|
|
25
|
+
const paths = override.linter.rules.style.noRestrictedImports.options.paths
|
|
26
|
+
|
|
27
|
+
// Should include both global and override paths
|
|
28
|
+
expect(paths).toMatchObject({
|
|
29
|
+
lodash: 'Use lodash-es instead',
|
|
30
|
+
moment: 'Use date-fns instead',
|
|
31
|
+
react: 'Use preact in tests',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Should not include marker
|
|
35
|
+
expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('should handle "off" overrides correctly', () => {
|
|
39
|
+
const result = processFixture('off-override-config.jsonc')
|
|
40
|
+
|
|
41
|
+
const override = result.overrides[0]
|
|
42
|
+
const paths = override.linter.rules.style.noRestrictedImports.options.paths
|
|
43
|
+
|
|
44
|
+
// Should include global paths except the one turned off
|
|
45
|
+
expect(paths.lodash).toBe('Use lodash-es instead')
|
|
46
|
+
expect(paths.moment).toBe('Use date-fns instead')
|
|
47
|
+
|
|
48
|
+
// Should not include the "off" path
|
|
49
|
+
expect(paths.jquery).toBeUndefined()
|
|
50
|
+
|
|
51
|
+
// Should include override-specific path
|
|
52
|
+
expect(paths.axios).toBe('Use fetch instead')
|
|
53
|
+
|
|
54
|
+
// Should not include marker
|
|
55
|
+
expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('Array Marker Resolution', () => {
|
|
60
|
+
test('should merge global array with override array', () => {
|
|
61
|
+
const result = processFixture('array-merge-config.jsonc')
|
|
62
|
+
|
|
63
|
+
const override = result.overrides[0]
|
|
64
|
+
const patterns = override.linter.rules.style.noRestrictedImports.options.patterns
|
|
65
|
+
|
|
66
|
+
// Should include both global and override items
|
|
67
|
+
expect(patterns).toEqual(
|
|
68
|
+
expect.arrayContaining([
|
|
69
|
+
{
|
|
70
|
+
group: ['global/*'],
|
|
71
|
+
message: 'Please do not import from global',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
group: ['localStorage/*'],
|
|
75
|
+
message: 'Please do not import from localStorage',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
group: ['sessionStorage/*'],
|
|
79
|
+
message: 'Please do not import from sessionStorage',
|
|
80
|
+
},
|
|
81
|
+
])
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Should not include marker
|
|
85
|
+
expect(patterns).not.toContain('__INCLUDE_GLOBAL_VALUES__')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('should deduplicate merged arrays', () => {
|
|
89
|
+
const config = {
|
|
90
|
+
linter: {
|
|
91
|
+
rules: {
|
|
92
|
+
style: {
|
|
93
|
+
noRestrictedImports: {
|
|
94
|
+
level: 'error',
|
|
95
|
+
options: {
|
|
96
|
+
patterns: [
|
|
97
|
+
{
|
|
98
|
+
group: ['event/*'],
|
|
99
|
+
message: 'Do not import from event',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
group: ['name/*'],
|
|
103
|
+
message: 'Do not import from name',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
overrides: [
|
|
112
|
+
{
|
|
113
|
+
include: ['src/**'],
|
|
114
|
+
linter: {
|
|
115
|
+
rules: {
|
|
116
|
+
style: {
|
|
117
|
+
noRestrictedImports: {
|
|
118
|
+
level: 'error',
|
|
119
|
+
options: {
|
|
120
|
+
patterns: [
|
|
121
|
+
'__INCLUDE_GLOBAL_VALUES__',
|
|
122
|
+
{
|
|
123
|
+
group: ['event/*'],
|
|
124
|
+
message: 'Do not import from event',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
group: ['localStorage/*'],
|
|
128
|
+
message: 'Do not import from localStorage',
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const globalRules = extractGlobalRuleValues(config)
|
|
141
|
+
const result = processConfig(config, globalRules)
|
|
142
|
+
|
|
143
|
+
const patterns = result.overrides[0].linter.rules.style.noRestrictedImports.options.patterns
|
|
144
|
+
|
|
145
|
+
// Should not have duplicates
|
|
146
|
+
const eventPatterns = patterns.filter((x) => JSON.stringify(x.group) === JSON.stringify(['event/*']))
|
|
147
|
+
expect(eventPatterns).toHaveLength(1)
|
|
148
|
+
|
|
149
|
+
// Should maintain override order (local items first)
|
|
150
|
+
expect(patterns[0]).toMatchObject({
|
|
151
|
+
group: ['event/*'],
|
|
152
|
+
message: 'Do not import from event',
|
|
153
|
+
})
|
|
154
|
+
expect(patterns[1]).toMatchObject({
|
|
155
|
+
group: ['localStorage/*'],
|
|
156
|
+
message: 'Do not import from localStorage',
|
|
157
|
+
})
|
|
158
|
+
expect(patterns[2]).toMatchObject({
|
|
159
|
+
group: ['name/*'],
|
|
160
|
+
message: 'Do not import from name',
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('No Markers', () => {
|
|
166
|
+
test('should not modify overrides without markers', () => {
|
|
167
|
+
const result = processFixture('no-markers-config.jsonc')
|
|
168
|
+
|
|
169
|
+
const override = result.overrides[0]
|
|
170
|
+
const paths = override.linter.rules.style.noRestrictedImports.options.paths
|
|
171
|
+
|
|
172
|
+
// Should only have override path
|
|
173
|
+
expect(paths).toMatchObject({
|
|
174
|
+
react: 'Custom restriction',
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Should not include global paths
|
|
178
|
+
expect(paths.lodash).toBeUndefined()
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('Edge Cases', () => {
|
|
183
|
+
test('should handle config with no overrides', () => {
|
|
184
|
+
const config = {
|
|
185
|
+
linter: {
|
|
186
|
+
rules: {
|
|
187
|
+
style: {
|
|
188
|
+
noRestrictedImports: {
|
|
189
|
+
level: 'error',
|
|
190
|
+
options: {
|
|
191
|
+
paths: {
|
|
192
|
+
lodash: 'Use lodash-es',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const globalRules = extractGlobalRuleValues(config)
|
|
202
|
+
const result = processConfig(config, globalRules)
|
|
203
|
+
|
|
204
|
+
// Should return unchanged
|
|
205
|
+
expect(result).toEqual(config)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('should handle override without linter rules', () => {
|
|
209
|
+
const config = {
|
|
210
|
+
linter: {
|
|
211
|
+
rules: {
|
|
212
|
+
style: {
|
|
213
|
+
noRestrictedImports: {
|
|
214
|
+
level: 'error',
|
|
215
|
+
options: {
|
|
216
|
+
paths: {
|
|
217
|
+
lodash: 'Use lodash-es',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
overrides: [
|
|
225
|
+
{
|
|
226
|
+
include: ['src/**'],
|
|
227
|
+
formatter: {
|
|
228
|
+
enabled: false,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const globalRules = extractGlobalRuleValues(config)
|
|
235
|
+
const result = processConfig(config, globalRules)
|
|
236
|
+
|
|
237
|
+
// Should handle override without linter rules gracefully
|
|
238
|
+
expect(result.overrides[0]).toMatchObject({
|
|
239
|
+
include: ['src/**'],
|
|
240
|
+
formatter: {
|
|
241
|
+
enabled: false,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('should handle empty global rules', () => {
|
|
247
|
+
const config = {
|
|
248
|
+
linter: {
|
|
249
|
+
rules: {},
|
|
250
|
+
},
|
|
251
|
+
overrides: [
|
|
252
|
+
{
|
|
253
|
+
include: ['src/**'],
|
|
254
|
+
linter: {
|
|
255
|
+
rules: {
|
|
256
|
+
style: {
|
|
257
|
+
noRestrictedImports: {
|
|
258
|
+
level: 'error',
|
|
259
|
+
options: {
|
|
260
|
+
paths: {
|
|
261
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
262
|
+
react: 'Custom restriction',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const globalRules = extractGlobalRuleValues(config)
|
|
274
|
+
const result = processConfig(config, globalRules)
|
|
275
|
+
|
|
276
|
+
const paths = result.overrides[0].linter.rules.style.noRestrictedImports.options.paths
|
|
277
|
+
|
|
278
|
+
// Should only include override paths
|
|
279
|
+
expect(paths).toMatchObject({
|
|
280
|
+
react: 'Custom restriction',
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// Should remove marker
|
|
284
|
+
expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe('Immutability', () => {
|
|
289
|
+
test('should not mutate original config', () => {
|
|
290
|
+
const originalConfig = {
|
|
291
|
+
linter: {
|
|
292
|
+
rules: {
|
|
293
|
+
style: {
|
|
294
|
+
noRestrictedImports: {
|
|
295
|
+
level: 'error',
|
|
296
|
+
options: {
|
|
297
|
+
paths: {
|
|
298
|
+
lodash: 'Use lodash-es',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
overrides: [
|
|
306
|
+
{
|
|
307
|
+
include: ['src/**'],
|
|
308
|
+
linter: {
|
|
309
|
+
rules: {
|
|
310
|
+
style: {
|
|
311
|
+
noRestrictedImports: {
|
|
312
|
+
level: 'error',
|
|
313
|
+
options: {
|
|
314
|
+
paths: {
|
|
315
|
+
__INCLUDE_GLOBAL_VALUES__: true,
|
|
316
|
+
react: 'Custom restriction',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const globalRules = extractGlobalRuleValues(originalConfig)
|
|
328
|
+
processConfig(originalConfig, globalRules)
|
|
329
|
+
|
|
330
|
+
// Original config should still have the marker
|
|
331
|
+
expect(
|
|
332
|
+
originalConfig.overrides[0].linter.rules.style.noRestrictedImports.options.paths.__INCLUDE_GLOBAL_VALUES__
|
|
333
|
+
).toBe(true)
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
})
|