Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9852a31

Browse files
fix: deep merge behavior in flat config (#18065)
* fix: replicate eslintrc merge behavior in flat config* do not merge arrays* Remove unused code* test for undefined properties* fix an edge case with non-enumerable properties(cherry picked from commitf182114)Co-authored-by: Francesco Trotta <github@fasttime.org>
1 parentdca7d0f commit9852a31

File tree

2 files changed

+157
-56
lines changed

2 files changed

+157
-56
lines changed

‎lib/config/flat-config-schema.js‎

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ function isNonNullObject(value) {
5353
returntypeofvalue==="object"&&value!==null;
5454
}
5555

56+
/**
57+
* Check if a value is a non-null non-array object.
58+
*@param {any} value The value to check.
59+
*@returns {boolean} `true` if the value is a non-null non-array object.
60+
*/
61+
functionisNonArrayObject(value){
62+
returnisNonNullObject(value)&&!Array.isArray(value);
63+
}
64+
5665
/**
5766
* Check if a value is undefined.
5867
*@param {any} value The value to check.
@@ -62,25 +71,14 @@ function isUndefined(value) {
6271
returntypeofvalue==="undefined";
6372
}
6473

65-
// A unique empty object to be used internally as a mapping key in `deepMerge`.
66-
constEMPTY_OBJECT={};
67-
6874
/**
69-
* Deeply merges two objects.
75+
* Deeply merges twonon-arrayobjects.
7076
*@param {Object} first The base object.
71-
*@param {any} second The overridesvalue.
77+
*@param {Object} second The overridesobject.
7278
*@param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
7379
*@returns {Object} An object with properties from both first and second.
7480
*/
75-
functiondeepMerge(first,second={},mergeMap=newMap()){
76-
77-
/*
78-
* If the second value is an array, just return it. We don't merge
79-
* arrays because order matters and we can't know the correct order.
80-
*/
81-
if(Array.isArray(second)){
82-
returnsecond;
83-
}
81+
functiondeepMerge(first,second,mergeMap=newMap()){
8482

8583
letsecondMergeMap=mergeMap.get(first);
8684

@@ -98,7 +96,7 @@ function deepMerge(first, second = {}, mergeMap = new Map()) {
9896
}
9997

10098
/*
101-
* First create a result object where properties from the secondvalue
99+
* First create a result object where properties from the secondobject
102100
* overwrite properties from the first. This sets up a baseline to use
103101
* later rather than needing to inspect and change every property
104102
* individually.
@@ -108,27 +106,25 @@ function deepMerge(first, second = {}, mergeMap = new Map()) {
108106
...second
109107
};
110108

109+
deleteresult.__proto__;// eslint-disable-line no-proto -- don't merge own property "__proto__"
110+
111111
// Store the pending result for this combination of first and second arguments.
112112
secondMergeMap.set(second,result);
113113

114114
for(constkeyofObject.keys(second)){
115115

116116
// avoid hairy edge case
117-
if(key==="__proto__"){
117+
if(key==="__proto__"||!Object.prototype.propertyIsEnumerable.call(first,key)){
118118
continue;
119119
}
120120

121121
constfirstValue=first[key];
122122
constsecondValue=second[key];
123123

124-
if(isNonNullObject(firstValue)){
124+
if(isNonArrayObject(firstValue)&&isNonArrayObject(secondValue)){
125125
result[key]=deepMerge(firstValue,secondValue,mergeMap);
126-
}elseif(isUndefined(firstValue)){
127-
if(isNonNullObject(secondValue)){
128-
result[key]=deepMerge(EMPTY_OBJECT,secondValue,mergeMap);
129-
}elseif(!isUndefined(secondValue)){
130-
result[key]=secondValue;
131-
}
126+
}elseif(isUndefined(secondValue)){
127+
result[key]=firstValue;
132128
}
133129
}
134130

‎tests/lib/config/flat-config-schema.js‎

Lines changed: 138 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@
77

88
const{ flatConfigSchema}=require("../../../lib/config/flat-config-schema");
99
const{ assert}=require("chai");
10+
const{Legacy:{ ConfigArray}}=require("@eslint/eslintrc");
11+
12+
/**
13+
* This function checks the result of merging two values in eslintrc config.
14+
* It uses deep strict equality to compare the actual and the expected results.
15+
* This is useful to ensure that the flat config merge logic behaves similarly to the old logic.
16+
* When eslintrc is removed, this function and its invocations can be also removed.
17+
*@param {Object} [first] The base object.
18+
*@param {Object} [second] The overrides object.
19+
*@param {Object} [expectedResult] The expected reults of merging first and second values.
20+
*@returns {void}
21+
*/
22+
functionconfirmLegacyMergeResult(first,second,expectedResult){
23+
constconfigArray=newConfigArray(
24+
{settings:first},
25+
{settings:second}
26+
);
27+
constconfig=configArray.extractConfig("/file");
28+
constactualResult=config.settings;
29+
30+
assert.deepStrictEqual(actualResult,expectedResult);
31+
}
1032

1133
describe("merge",()=>{
1234

@@ -18,36 +40,14 @@ describe("merge", () => {
1840
constresult=merge(first,second);
1941

2042
assert.deepStrictEqual(result,{ ...first, ...second});
21-
});
22-
23-
it("overrides an object with an array",()=>{
24-
constfirst={foo:42};
25-
constsecond=["bar","baz"];
26-
constresult=merge(first,second);
27-
28-
assert.strictEqual(result,second);
29-
});
30-
31-
it("merges an array with an object",()=>{
32-
constfirst=["foo","bar"];
33-
constsecond={baz:42};
34-
constresult=merge(first,second);
35-
36-
assert.deepStrictEqual(result,{0:"foo",1:"bar",baz:42});
37-
});
38-
39-
it("overrides an array with another array",()=>{
40-
constfirst=["foo","bar"];
41-
constsecond=["baz","qux"];
42-
constresult=merge(first,second);
43-
44-
assert.strictEqual(result,second);
43+
confirmLegacyMergeResult(first,second,result);
4544
});
4645

4746
it("returns an emtpy object if both values are undefined",()=>{
4847
constresult=merge(void0,void0);
4948

5049
assert.deepStrictEqual(result,{});
50+
confirmLegacyMergeResult(void0,void0,result);
5151
});
5252

5353
it("returns an object equal to the first one if the second one is undefined",()=>{
@@ -56,6 +56,7 @@ describe("merge", () => {
5656

5757
assert.deepStrictEqual(result,first);
5858
assert.notStrictEqual(result,first);
59+
confirmLegacyMergeResult(first,void0,result);
5960
});
6061

6162
it("returns an object equal to the second one if the first one is undefined",()=>{
@@ -64,6 +65,16 @@ describe("merge", () => {
6465

6566
assert.deepStrictEqual(result,second);
6667
assert.notStrictEqual(result,second);
68+
confirmLegacyMergeResult(void0,second,result);
69+
});
70+
71+
it("does not preserve the type of merged objects",()=>{
72+
constfirst=newSet(["foo","bar"]);
73+
constsecond=newSet(["baz"]);
74+
constresult=merge(first,second);
75+
76+
assert.deepStrictEqual(result,{});
77+
confirmLegacyMergeResult(first,second,result);
6778
});
6879

6980
it("merges two objects in a property",()=>{
@@ -72,6 +83,34 @@ describe("merge", () => {
7283
constresult=merge(first,second);
7384

7485
assert.deepStrictEqual(result,{foo:{bar:"baz",qux:42}});
86+
confirmLegacyMergeResult(first,second,result);
87+
});
88+
89+
it("overwrites an object in a property with an array",()=>{
90+
constfirst={someProperty:{1:"foo",bar:"baz"}};
91+
constsecond={someProperty:["qux"]};
92+
constresult=merge(first,second);
93+
94+
assert.deepStrictEqual(result,second);
95+
assert.strictEqual(result.someProperty,second.someProperty);
96+
});
97+
98+
it("overwrites an array in a property with another array",()=>{
99+
constfirst={someProperty:["foo","bar",void0,"baz"]};
100+
constsecond={someProperty:["qux",void0,42]};
101+
constresult=merge(first,second);
102+
103+
assert.deepStrictEqual(result,second);
104+
assert.strictEqual(result.someProperty,second.someProperty);
105+
});
106+
107+
it("overwrites an array in a property with an object",()=>{
108+
constfirst={foo:["foobar"]};
109+
constsecond={foo:{1:"qux",bar:"baz"}};
110+
constresult=merge(first,second);
111+
112+
assert.deepStrictEqual(result,second);
113+
assert.strictEqual(result.foo,second.foo);
75114
});
76115

77116
it("does not override a value in a property with undefined",()=>{
@@ -81,6 +120,7 @@ describe("merge", () => {
81120

82121
assert.deepStrictEqual(result,first);
83122
assert.notStrictEqual(result,first);
123+
confirmLegacyMergeResult(first,second,result);
84124
});
85125

86126
it("does not change the prototype of a merged object",()=>{
@@ -89,39 +129,104 @@ describe("merge", () => {
89129
constresult=merge(first,second);
90130

91131
assert.strictEqual(Object.getPrototypeOf(result),Object.prototype);
132+
confirmLegacyMergeResult(first,second,result);
92133
});
93134

94135
it("does not merge the '__proto__' property",()=>{
95136
constfirst={["__proto__"]:{foo:42}};
96137
constsecond={["__proto__"]:{bar:"baz"}};
97138
constresult=merge(first,second);
98139

99-
assert.deepStrictEqual(result,second);
100-
assert.notStrictEqual(result,second);
140+
assert.deepStrictEqual(result,{});
141+
confirmLegacyMergeResult(first,second,result);
101142
});
102143

103-
it("throws an error ifa value in a property is overriden with null",()=>{
144+
it("overridesa value in a property with null",()=>{
104145
constfirst={foo:{bar:"baz"}};
105146
constsecond={foo:null};
147+
constresult=merge(first,second);
106148

107-
assert.throws(()=>merge(first,second),TypeError);
149+
assert.deepStrictEqual(result,second);
150+
assert.notStrictEqual(result,second);
151+
confirmLegacyMergeResult(first,second,result);
108152
});
109153

110-
it("does not overridea value in a property with a primitive",()=>{
154+
it("overridesa value in a property with a non-nullish primitive",()=>{
111155
constfirst={foo:{bar:"baz"}};
112156
constsecond={foo:42};
113157
constresult=merge(first,second);
114158

115-
assert.deepStrictEqual(result,first);
116-
assert.notStrictEqual(result,first);
159+
assert.deepStrictEqual(result,second);
160+
assert.notStrictEqual(result,second);
161+
confirmLegacyMergeResult(first,second,result);
117162
});
118163

119-
it("merges an object in a property with a string",()=>{
164+
it("overrides an object in a property with a string",()=>{
120165
constfirst={foo:{bar:"baz"}};
121166
constsecond={foo:"qux"};
122167
constresult=merge(first,second);
123168

124-
assert.deepStrictEqual(result,{foo:{0:"q",1:"u",2:"x",bar:"baz"}});
169+
assert.deepStrictEqual(result,second);
170+
assert.notStrictEqual(result,first);
171+
confirmLegacyMergeResult(first,second,result);
172+
});
173+
174+
it("overrides a value in a property with a function",()=>{
175+
constfirst={someProperty:{foo:42}};
176+
constsecond={someProperty(){}};
177+
constresult=merge(first,second);
178+
179+
assert.deepStrictEqual(result,second);
180+
assert.notProperty(result.someProperty,"foo");
181+
confirmLegacyMergeResult(first,second,result);
182+
});
183+
184+
it("overrides a function in a property with an object",()=>{
185+
constfirst={someProperty:Object.assign(()=>{},{foo:"bar"})};
186+
constsecond={someProperty:{baz:"qux"}};
187+
constresult=merge(first,second);
188+
189+
assert.deepStrictEqual(result,second);
190+
assert.notProperty(result.someProperty,"foo");
191+
confirmLegacyMergeResult(first,second,result);
192+
});
193+
194+
it("sets properties to undefined",()=>{
195+
constfirst={foo:void0,bar:void0};
196+
constsecond={foo:void0,baz:void0};
197+
constresult=merge(first,second);
198+
199+
assert.deepStrictEqual(result,{foo:void0,bar:void0,baz:void0});
200+
});
201+
202+
it("considers only own enumerable properties",()=>{
203+
constfirst={
204+
__proto__:{inherited1:"A"},// non-own properties are not considered
205+
included1:"B",
206+
notMerged1:{first:true}
207+
};
208+
constsecond={
209+
__proto__:{inherited2:"C"},// non-own properties are not considered
210+
included2:"D",
211+
notMerged2:{second:true}
212+
};
213+
214+
// non-enumerable properties are not considered
215+
Object.defineProperty(first,"notMerged2",{enumerable:false,value:{first:true}});
216+
Object.defineProperty(second,"notMerged1",{enumerable:false,value:{second:true}});
217+
218+
constresult=merge(first,second);
219+
220+
assert.deepStrictEqual(
221+
result,
222+
{
223+
included1:"B",
224+
included2:"D",
225+
notMerged1:{first:true},
226+
notMerged2:{second:true}
227+
}
228+
);
229+
confirmLegacyMergeResult(first,second,result);
125230
});
126231

127232
it("merges objects with self-references",()=>{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp