Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6a71284

Browse files
FrozenPandazvsavkin
authored andcommittedMar 10, 2020
fix(core): fix json diff and implicitJsonChanges
1 parent 9b9e2bf commit 6a71284

File tree

9 files changed

+267
-34
lines changed

9 files changed

+267
-34
lines changed
 

‎packages/workspace/src/core/affected-project-graph/locators/implicit-json-changes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@ function getTouchedProjects(path: string[], implicitDependencyConfig: any) {
6565
break;
6666
}
6767
}
68-
return found ? curr : [];
68+
return found && Array.isArray(curr) ? curr : [];
6969
}

‎packages/workspace/src/core/affected-project-graph/locators/npm-packages.spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,37 @@ describe('getTouchedNpmPackages', () => {
9090
expect(result).toEqual(['proj1', 'proj2']);
9191
});
9292

93+
it('should handle package addition', () => {
94+
const result = getTouchedNpmPackages(
95+
[
96+
{
97+
file: 'package.json',
98+
mtime: 0,
99+
ext: '.json',
100+
getChanges: () => [
101+
{
102+
type: DiffType.Added,
103+
path: ['dependencies', 'awesome-nrwl'],
104+
value: {
105+
lhs: undefined,
106+
rhs: '0.0.1'
107+
}
108+
}
109+
]
110+
}
111+
],
112+
workspaceJson,
113+
nxJson,
114+
{
115+
dependencies: {
116+
'happy-nrwl': '0.0.2',
117+
'awesome-nrwl': '0.0.1'
118+
}
119+
}
120+
);
121+
expect(result).toEqual(['awesome-nrwl']);
122+
});
123+
93124
it('should handle whole file changes', () => {
94125
const result = getTouchedNpmPackages(
95126
[

‎packages/workspace/src/core/affected-project-graph/locators/npm-packages.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
1414
for (const c of changes) {
1515
if (
1616
isJsonChange(c) &&
17-
(c.path[0] === 'dependencies' || c.path[0] === 'devDependencies')
17+
(c.path[0] === 'dependencies' || c.path[0] === 'devDependencies') &&
18+
c.path.length === 2
1819
) {
1920
// A package was deleted so mark all workspace projects as touched.
2021
if (c.type === DiffType.Deleted) {

‎packages/workspace/src/core/affected-project-graph/locators/nx-json-changes.spec.ts

+33-3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ describe('getTouchedProjectsInNxJson', () => {
9595
ext: '.json',
9696
mtime: 0,
9797
getChanges: () => [
98+
{
99+
type: DiffType.Added,
100+
path: ['projects', 'proj1'],
101+
value: {
102+
lhs: undefined,
103+
rhs: {
104+
tags: []
105+
}
106+
}
107+
},
98108
{
99109
type: DiffType.Added,
100110
path: ['projects', 'proj1', 'tags'],
@@ -122,7 +132,7 @@ describe('getTouchedProjectsInNxJson', () => {
122132
expect(result).toEqual(['proj1']);
123133
});
124134

125-
it('should not return projects removed in nx.json', () => {
135+
it('should return all projects when a project is removed', () => {
126136
const result = getTouchedProjectsInNxJson(
127137
[
128138
{
@@ -132,9 +142,11 @@ describe('getTouchedProjectsInNxJson', () => {
132142
getChanges: () => [
133143
{
134144
type: DiffType.Deleted,
135-
path: ['projects', 'proj3', 'tags'],
145+
path: ['projects', 'proj3'],
136146
value: {
137-
lhs: [],
147+
lhs: {
148+
tags: []
149+
},
138150
rhs: undefined
139151
}
140152
}
@@ -165,6 +177,24 @@ describe('getTouchedProjectsInNxJson', () => {
165177
ext: '.json',
166178
mtime: 0,
167179
getChanges: () => [
180+
{
181+
type: DiffType.Modified,
182+
path: ['projects', 'proj1'],
183+
value: {
184+
lhs: { tags: ['scope:feat'] },
185+
rhs: {
186+
tags: ['scope:shared']
187+
}
188+
}
189+
},
190+
{
191+
type: DiffType.Modified,
192+
path: ['projects', 'proj1', 'tags'],
193+
value: {
194+
lhs: ['scope:feat'],
195+
rhs: ['scope:shared']
196+
}
197+
},
168198
{
169199
type: DiffType.Modified,
170200
path: ['projects', 'proj1', 'tags', '0'],

‎packages/workspace/src/core/affected-project-graph/locators/nx-json-changes.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,21 @@ export const getTouchedProjectsInNxJson: TouchedProjectLocator<
3333
continue;
3434
}
3535

36-
if (nxJson.projects[change.path[1]]) {
37-
touched.push(change.path[1]);
38-
} else {
39-
// The project was deleted so affect all projects
40-
touched.push(...Object.keys(nxJson.projects));
41-
// Break out of the loop after all projects have been added.
42-
break;
36+
// Only look for changes that are changes to the whole project definition
37+
if (change.path.length !== 2) {
38+
continue;
39+
}
40+
41+
switch (change.type) {
42+
case DiffType.Deleted: {
43+
// We are not sure which projects used to depend on a deleted project
44+
// so return all projects to be safe
45+
return Object.keys(nxJson.projects);
46+
}
47+
default: {
48+
// Add the project name
49+
touched.push(change.path[1]);
50+
}
4351
}
4452
}
4553
return touched;

‎packages/workspace/src/core/affected-project-graph/locators/workspace-json-changes.spec.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,21 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
9595
getChanges: () => [
9696
{
9797
type: DiffType.Added,
98-
path: ['projects', 'proj1', 'tags'],
98+
path: ['projects', 'proj1'],
9999
value: {
100100
lhs: undefined,
101-
rhs: []
101+
rhs: {
102+
root: 'proj1'
103+
}
104+
}
105+
},
106+
107+
{
108+
type: DiffType.Added,
109+
path: ['projects', 'proj1', 'root'],
110+
value: {
111+
lhs: undefined,
112+
rhs: 'proj1'
102113
}
103114
}
104115
]
@@ -125,9 +136,11 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
125136
getChanges: () => [
126137
{
127138
type: DiffType.Deleted,
128-
path: ['projects', 'proj3', 'root'],
139+
path: ['projects', 'proj3'],
129140
value: {
130-
lhs: 'proj3',
141+
lhs: {
142+
root: 'proj3'
143+
},
131144
rhs: undefined
132145
}
133146
}
@@ -156,6 +169,18 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
156169
ext: '.json',
157170
mtime: 0,
158171
getChanges: () => [
172+
{
173+
type: DiffType.Modified,
174+
path: ['projects', 'proj1'],
175+
value: {
176+
lhs: {
177+
root: 'proj3'
178+
},
179+
rhs: {
180+
root: 'proj1'
181+
}
182+
}
183+
},
159184
{
160185
type: DiffType.Modified,
161186
path: ['projects', 'proj1', 'root'],

‎packages/workspace/src/core/affected-project-graph/locators/workspace-json-changes.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
WholeFileChange,
44
workspaceFileName
55
} from '../../file-utils';
6-
import { isJsonChange, JsonChange } from '../../../utils/json-diff';
6+
import { DiffType, isJsonChange, JsonChange } from '../../../utils/json-diff';
77
import { TouchedProjectLocator } from '../affected-project-graph-models';
88

99
export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
@@ -39,13 +39,21 @@ export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
3939
continue;
4040
}
4141

42-
if (workspaceJson.projects[change.path[1]]) {
43-
touched.push(change.path[1]);
44-
} else {
45-
// The project was deleted so affect all projects
46-
touched.push(...Object.keys(workspaceJson.projects));
47-
// Break out of the loop after all projects have been added.
48-
break;
42+
// Only look for changes that are changes to the whole project definition
43+
if (change.path.length !== 2) {
44+
continue;
45+
}
46+
47+
switch (change.type) {
48+
case DiffType.Deleted: {
49+
// We are not sure which projects used to depend on a deleted project
50+
// so return all projects to be safe
51+
return Object.keys(workspaceJson.projects);
52+
}
53+
default: {
54+
// Add the project name
55+
touched.push(change.path[1]);
56+
}
4957
}
5058
}
5159
return touched;

‎packages/workspace/src/utils/json-diff.spec.ts

+108-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
11
import { jsonDiff, DiffType } from './json-diff';
22

33
describe('jsonDiff', () => {
4-
it('should return deep diffs of two JSON objects', () => {
4+
it('should return deep diffs of two JSON objects (including parents of children changes)', () => {
55
const result = jsonDiff(
66
{ x: 1, a: { b: { c: 1 } } },
77
{ y: 2, a: { b: { c: 2, d: 2 } } }
88
);
99

1010
expect(result).toEqual(
1111
expect.arrayContaining([
12+
{
13+
type: DiffType.Modified,
14+
path: ['a'],
15+
value: {
16+
lhs: {
17+
b: {
18+
c: 1
19+
}
20+
},
21+
rhs: {
22+
b: {
23+
c: 2,
24+
d: 2
25+
}
26+
}
27+
}
28+
},
29+
{
30+
type: DiffType.Modified,
31+
path: ['a', 'b'],
32+
value: {
33+
lhs: {
34+
c: 1
35+
},
36+
rhs: {
37+
c: 2,
38+
d: 2
39+
}
40+
}
41+
},
1242
{
1343
type: DiffType.Modified,
1444
path: ['a', 'b', 'c'],
@@ -33,6 +63,83 @@ describe('jsonDiff', () => {
3363
);
3464
});
3565

66+
it('should have diffs for objects as well', () => {
67+
const result = jsonDiff(
68+
{
69+
a: { b: 0 }
70+
},
71+
72+
{
73+
a: { b: 1 }
74+
}
75+
);
76+
expect(result).toContainEqual({
77+
type: DiffType.Modified,
78+
path: ['a'],
79+
value: {
80+
lhs: {
81+
b: 0
82+
},
83+
rhs: {
84+
b: 1
85+
}
86+
}
87+
});
88+
expect(result).toContainEqual({
89+
type: DiffType.Modified,
90+
path: ['a', 'b'],
91+
value: {
92+
lhs: 0,
93+
rhs: 1
94+
}
95+
});
96+
97+
const result2 = jsonDiff(
98+
{},
99+
100+
{
101+
a: {}
102+
}
103+
);
104+
expect(result2).toContainEqual({
105+
type: DiffType.Added,
106+
path: ['a'],
107+
value: { lhs: undefined, rhs: {} }
108+
});
109+
});
110+
111+
it('should work for added array items', () => {
112+
const result = jsonDiff(
113+
{
114+
rules: ['rule1']
115+
},
116+
{
117+
rules: ['rule1', 'rule2']
118+
}
119+
);
120+
121+
expect(result).toEqual(
122+
expect.arrayContaining([
123+
{
124+
type: DiffType.Modified,
125+
path: ['rules'],
126+
value: {
127+
lhs: ['rule1'],
128+
rhs: ['rule1', 'rule2']
129+
}
130+
},
131+
{
132+
type: DiffType.Added,
133+
path: ['rules', '1'],
134+
value: {
135+
lhs: undefined,
136+
rhs: 'rule2'
137+
}
138+
}
139+
])
140+
);
141+
});
142+
36143
it('should work well for package.json', () => {
37144
const result = jsonDiff(
38145
{

‎packages/workspace/src/utils/json-diff.ts

+32-9
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
2929

3030
walkJsonTree(lhs, [], (path, lhsValue) => {
3131
seenInLhs.add(hashArray(path));
32-
if (typeof lhsValue === 'object') {
33-
return true;
34-
}
3532
const rhsValue = getJsonValue(path, rhs);
3633
if (rhsValue === undefined) {
3734
result.push({
@@ -42,7 +39,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
4239
rhs: undefined
4340
}
4441
});
45-
} else if (lhsValue !== rhsValue) {
42+
} else if (!deepEquals(lhsValue, rhsValue)) {
4643
result.push({
4744
type: DiffType.Modified,
4845
path,
@@ -52,13 +49,10 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
5249
}
5350
});
5451
}
55-
return false;
52+
return typeof lhsValue === 'object';
5653
});
5754

5855
walkJsonTree(rhs, [], (path, rhsValue) => {
59-
if (typeof rhsValue === 'object') {
60-
return true;
61-
}
6256
const addedInRhs = !seenInLhs.has(hashArray(path));
6357
if (addedInRhs) {
6458
result.push({
@@ -71,6 +65,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
7165
});
7266
return false;
7367
}
68+
return typeof rhsValue === 'object';
7469
});
7570

7671
return result;
@@ -85,7 +80,6 @@ export function walkJsonTree(
8580
if (!json || typeof json !== 'object') {
8681
return;
8782
}
88-
8983
Object.keys(json).forEach(key => {
9084
const path = currPath.concat([key]);
9185
const shouldContinue = visitor(path, json[key]);
@@ -109,3 +103,32 @@ function getJsonValue(path: string[], json: any): void | any {
109103
}
110104
return curr;
111105
}
106+
107+
function deepEquals(a: any, b: any): boolean {
108+
if (a === b) {
109+
return true;
110+
}
111+
112+
// Values do not need to be checked for deep equality and the above is false
113+
if (
114+
// Values are different types
115+
typeof a !== typeof b ||
116+
// Values are the same type but not an object or array
117+
(typeof a !== 'object' && !Array.isArray(a)) ||
118+
// Objects are the same type, objects or arrays, but do not have the same number of keys
119+
Object.keys(a).length !== Object.keys(b).length
120+
) {
121+
return false;
122+
}
123+
124+
// Values need to be checked for deep equality
125+
return Object.entries(a).reduce((equal, [key, aValue]) => {
126+
// Skip other keys if it is already not equal.
127+
if (!equal) {
128+
return equal;
129+
}
130+
131+
// Traverse the object
132+
return deepEquals(aValue, b[key]);
133+
}, true);
134+
}

0 commit comments

Comments
 (0)
Please sign in to comment.