jsii-diff 1.118.0 → 1.120.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/README.md +39 -0
- package/bin/jsii-diff.js +34 -2
- package/lib/type-analysis.d.ts +35 -30
- package/lib/type-analysis.js +160 -155
- package/lib/type-comparison.d.ts +7 -1
- package/lib/type-comparison.js +38 -19
- package/lib/types.d.ts +4 -0
- package/lib/validations.d.ts +5 -5
- package/lib/validations.js +17 -17
- package/lib/version.d.ts +1 -1
- package/lib/version.js +2 -2
- package/package.json +5 -5
- package/test/classes.test.js +10 -0
- package/test/util.d.ts +2 -2
- package/test/util.js +2 -2
package/README.md
CHANGED
|
@@ -126,6 +126,45 @@ abstract members yet.
|
|
|
126
126
|
for subclassing, but treating them as such would limit the evolvability of
|
|
127
127
|
libraries too much.
|
|
128
128
|
|
|
129
|
+
## Accepting breaking changes
|
|
130
|
+
|
|
131
|
+
Sometimes you want to move forward with a change, even if it would technically
|
|
132
|
+
be a breaking change to your API. In order to do that, run `jsii-diff`
|
|
133
|
+
with the flag `--keys`. It will then print an identifier for every violation
|
|
134
|
+
between square brackets, for example:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
$ jsii-diff <old> --keys
|
|
138
|
+
IFACE pkg.MyStruct: formerly required property 'prop' is optional: returned from pkg.IConsumer.someMethod [weakened:pkg.MyStruct]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
To accept a breaking finding, put the key (in this example `weakened:pkg.MyStruct`)
|
|
142
|
+
into a text file, for example `allowed-breaking-changes.txt`, and pass it to
|
|
143
|
+
`jsii-diff` as an ignore file:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
$ jsii-diff <old> --keys --ignore-file allowed-breaking-changes.txt
|
|
147
|
+
(no error)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Moving/renaming API elements
|
|
151
|
+
|
|
152
|
+
If you've moved API elements around between versions of your library, you can
|
|
153
|
+
put a special ignore marker starting with `move:` into your `--ignore-file`. To
|
|
154
|
+
separate the old and new class names, you can use `:`, `,` or whitespace.
|
|
155
|
+
|
|
156
|
+
For example:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
move:package.OldClassName package.NewClassName
|
|
160
|
+
move:package.OldClassName:package.NewClassName
|
|
161
|
+
move:package.OldClassName, package.NewClassName
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Moving API elements is always breaking, but using this feature you can confirm
|
|
165
|
+
that you at least didn't break anything in the API surface of the moved classes
|
|
166
|
+
themselves.
|
|
167
|
+
|
|
129
168
|
## Help! jsii-diff is marking my changes as breaking
|
|
130
169
|
|
|
131
170
|
See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for more information.
|
package/bin/jsii-diff.js
CHANGED
|
@@ -91,13 +91,16 @@ async function main() {
|
|
|
91
91
|
if (original.name !== updated.name) {
|
|
92
92
|
process.stderr.write(`Look like different assemblies: '${original.name}' vs '${updated.name}'. Comparing is probably pointless...\n`);
|
|
93
93
|
}
|
|
94
|
+
const allowedBreakingChanges = await loadFilter(argv['ignore-file']);
|
|
95
|
+
const fqnRemapping = extractFqnRemappings(allowedBreakingChanges);
|
|
94
96
|
LOG.info('Starting analysis');
|
|
95
97
|
const mismatches = (0, lib_1.compareAssemblies)(original, updated, {
|
|
96
98
|
defaultExperimental: argv['default-stability'] === 'experimental',
|
|
99
|
+
fqnRemapping,
|
|
97
100
|
});
|
|
98
101
|
LOG.info(`Found ${mismatches.count} issues`);
|
|
99
102
|
if (mismatches.count > 0) {
|
|
100
|
-
const diags = (0, diagnostics_1.classifyDiagnostics)(mismatches, (0, diagnostics_1.treatAsError)(argv['error-on'], argv['experimental-errors']),
|
|
103
|
+
const diags = (0, diagnostics_1.classifyDiagnostics)(mismatches, (0, diagnostics_1.treatAsError)(argv['error-on'], argv['experimental-errors']), allowedBreakingChanges);
|
|
101
104
|
process.stderr.write(`Original assembly: ${original.name}@${original.version}\n`);
|
|
102
105
|
process.stderr.write(`Updated assembly: ${updated.name}@${updated.version}\n`);
|
|
103
106
|
process.stderr.write('API elements with incompatible changes:\n');
|
|
@@ -108,6 +111,35 @@ async function main() {
|
|
|
108
111
|
}
|
|
109
112
|
return 0;
|
|
110
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Extract all lines that start with `move:` from the given string set
|
|
116
|
+
*
|
|
117
|
+
* Interpret them as `move:OLDFQN <sep> NEWFQN`, mapping moved FQNs.
|
|
118
|
+
*
|
|
119
|
+
* Separator can be any of `:`, comma or whitespace.
|
|
120
|
+
*
|
|
121
|
+
* Modifies the input set in-place.
|
|
122
|
+
*/
|
|
123
|
+
function extractFqnRemappings(allowedBreakingChanges) {
|
|
124
|
+
const ret = {};
|
|
125
|
+
for (const line of Array.from(allowedBreakingChanges)) {
|
|
126
|
+
const prefix = 'move:';
|
|
127
|
+
if (!line.startsWith(prefix)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const parts = line
|
|
131
|
+
.slice(prefix.length)
|
|
132
|
+
.trim()
|
|
133
|
+
.split(/[:, \t]+/g);
|
|
134
|
+
if (parts.length !== 2) {
|
|
135
|
+
throw new Error(`Invalid moved FQN declaration: ${line}. Expected format is 'move:old:new'`);
|
|
136
|
+
}
|
|
137
|
+
const [oldFqn, newFqn] = parts;
|
|
138
|
+
ret[oldFqn] = newFqn;
|
|
139
|
+
allowedBreakingChanges.delete(line);
|
|
140
|
+
}
|
|
141
|
+
return ret;
|
|
142
|
+
}
|
|
111
143
|
// Allow both npm:<package> (legacy) and npm://<package> (looks better)
|
|
112
144
|
const NPM_REGEX = /^npm:(\/\/)?/;
|
|
113
145
|
/**
|
|
@@ -234,7 +266,7 @@ async function loadFilter(filterFilename) {
|
|
|
234
266
|
return new Set((await fs.readFile(filterFilename, { encoding: 'utf-8' }))
|
|
235
267
|
.split('\n')
|
|
236
268
|
.map((x) => x.trim())
|
|
237
|
-
.filter((x) => !x.startsWith('#')));
|
|
269
|
+
.filter((x) => x && !x.startsWith('#')));
|
|
238
270
|
}
|
|
239
271
|
catch (e) {
|
|
240
272
|
if (e.code !== 'ENOENT') {
|
package/lib/type-analysis.d.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
import * as reflect from 'jsii-reflect';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
2
|
+
export declare class TypeAnalysis {
|
|
3
|
+
private readonly updatedSystem;
|
|
4
|
+
private readonly fqnRemapping;
|
|
5
|
+
constructor(updatedSystem: reflect.TypeSystem, fqnRemapping?: Record<string, string>);
|
|
6
|
+
/**
|
|
7
|
+
* Check whether A is a supertype of B
|
|
8
|
+
*
|
|
9
|
+
* Put differently: whether any value of type B would be assignable to a
|
|
10
|
+
* variable of type A.
|
|
11
|
+
*
|
|
12
|
+
* We always check the relationship in the NEW (latest, updated) typesystem.
|
|
13
|
+
*/
|
|
14
|
+
isSuperType(a: reflect.TypeReference, b: reflect.TypeReference): Analysis;
|
|
15
|
+
/**
|
|
16
|
+
* Find types A and B in the updated type system, and check whether they have a supertype relationship in the type system
|
|
17
|
+
*/
|
|
18
|
+
isNominalSuperType(a: reflect.TypeReference, b: reflect.TypeReference): Analysis;
|
|
19
|
+
/**
|
|
20
|
+
* Is struct A a structural supertype of struct B?
|
|
21
|
+
*
|
|
22
|
+
* Trying to answer the question, is this assignment legal for all values
|
|
23
|
+
* of `expr in B`.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const var: A = expr as B;
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* A is a structural supertype of B if all required members of A are also
|
|
30
|
+
* required in B, and of a compatible type.
|
|
31
|
+
*
|
|
32
|
+
* Nullable members of A must either not exist in B, or be of a compatible
|
|
33
|
+
* type.
|
|
34
|
+
*/
|
|
35
|
+
isStructuralSuperType(a: reflect.InterfaceType, b: reflect.InterfaceType): Analysis;
|
|
36
|
+
}
|
|
32
37
|
export type Analysis = {
|
|
33
38
|
success: true;
|
|
34
39
|
} | FailedAnalysis;
|
package/lib/type-analysis.js
CHANGED
|
@@ -1,180 +1,185 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.isNominalSuperType = isNominalSuperType;
|
|
5
|
-
exports.isStructuralSuperType = isStructuralSuperType;
|
|
3
|
+
exports.TypeAnalysis = void 0;
|
|
6
4
|
exports.prependReason = prependReason;
|
|
7
5
|
const util_1 = require("./util");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
class TypeAnalysis {
|
|
7
|
+
constructor(updatedSystem, fqnRemapping = {}) {
|
|
8
|
+
this.updatedSystem = updatedSystem;
|
|
9
|
+
this.fqnRemapping = fqnRemapping;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check whether A is a supertype of B
|
|
13
|
+
*
|
|
14
|
+
* Put differently: whether any value of type B would be assignable to a
|
|
15
|
+
* variable of type A.
|
|
16
|
+
*
|
|
17
|
+
* We always check the relationship in the NEW (latest, updated) typesystem.
|
|
18
|
+
*/
|
|
19
|
+
isSuperType(a, b) {
|
|
20
|
+
if (a.void || b.void) {
|
|
21
|
+
throw new Error('isSuperType() does not handle voids');
|
|
22
|
+
}
|
|
23
|
+
if (a.isAny) {
|
|
25
24
|
return { success: true };
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return failure(`${b.toString()} is not an array type`);
|
|
26
|
+
if (a.primitive !== undefined) {
|
|
27
|
+
if (a.primitive === b.primitive) {
|
|
28
|
+
return { success: true };
|
|
29
|
+
}
|
|
30
|
+
return failure(`${b.toString()} is not assignable to ${a.toString()}`);
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
32
|
+
if (a.arrayOfType !== undefined) {
|
|
33
|
+
// Arrays are covariant
|
|
34
|
+
if (b.arrayOfType === undefined) {
|
|
35
|
+
return failure(`${b.toString()} is not an array type`);
|
|
36
|
+
}
|
|
37
|
+
return prependReason(this.isSuperType(a.arrayOfType, b.arrayOfType), `${b.toString()} is not assignable to ${a.toString()}`);
|
|
40
38
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return { success: true };
|
|
39
|
+
if (a.mapOfType !== undefined) {
|
|
40
|
+
// Maps are covariant (are they?)
|
|
41
|
+
if (b.mapOfType === undefined) {
|
|
42
|
+
return failure(`${b.toString()} is not a map type`);
|
|
43
|
+
}
|
|
44
|
+
return prependReason(this.isSuperType(a.mapOfType, b.mapOfType), `${b.toString()} is not assignable to ${a.toString()}`);
|
|
48
45
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
// Every element of B can be assigned to A
|
|
47
|
+
if (b.unionOfTypes !== undefined) {
|
|
48
|
+
const analyses = b.unionOfTypes.map((bbb) => this.isSuperType(a, bbb));
|
|
49
|
+
if (analyses.every((x) => x.success)) {
|
|
50
|
+
return { success: true };
|
|
51
|
+
}
|
|
52
|
+
return failure(`some of ${b.toString()} are not assignable to ${a.toString()}`, ...(0, util_1.flatMap)(analyses, (x) => (x.success ? [] : x.reasons)));
|
|
53
|
+
}
|
|
54
|
+
// There should be an element of A which can accept all of B
|
|
55
|
+
if (a.unionOfTypes !== undefined) {
|
|
56
|
+
const analyses = a.unionOfTypes.map((aaa) => this.isSuperType(aaa, b));
|
|
57
|
+
if (analyses.some((x) => x.success)) {
|
|
58
|
+
return { success: true };
|
|
59
|
+
}
|
|
60
|
+
return failure(`none of ${b.toString()} are assignable to ${a.toString()}`, ...(0, util_1.flatMap)(analyses, (x) => (x.success ? [] : x.reasons)));
|
|
61
|
+
}
|
|
62
|
+
// intersection A ⊒ B <=> all of the elements of A ⊒ B
|
|
63
|
+
if (a.intersectionOfTypes !== undefined) {
|
|
64
|
+
const analyses = a.intersectionOfTypes.map((aaa) => this.isSuperType(aaa, b));
|
|
65
|
+
if (analyses.every((x) => x.success)) {
|
|
66
|
+
return { success: true };
|
|
67
|
+
}
|
|
68
|
+
return failure(`${b.toString()} is not assignable to all of ${a.toString()}`, ...(0, util_1.flatMap)(analyses, (x) => (x.success ? [] : x.reasons)));
|
|
69
|
+
}
|
|
70
|
+
// A ⊒ intersection B <=> A ⊒ any of the elements of B
|
|
71
|
+
if (b.intersectionOfTypes !== undefined) {
|
|
72
|
+
const analyses = b.intersectionOfTypes.map((bbb) => this.isSuperType(a, bbb));
|
|
73
|
+
if (analyses.some((x) => x.success)) {
|
|
74
|
+
return { success: true };
|
|
75
|
+
}
|
|
76
|
+
return failure(`some of ${b.toString()} are not assignable to ${a.toString()}`, ...(0, util_1.flatMap)(analyses, (x) => (x.success ? [] : x.reasons)));
|
|
77
|
+
}
|
|
78
|
+
// We have two named types, recursion might happen so protect against it.
|
|
79
|
+
try {
|
|
80
|
+
// For named types, we'll always do a nominal typing relationship.
|
|
81
|
+
// That is, if in the updated typesystem someone were to use the type name
|
|
82
|
+
// from the old assembly, do they have a typing relationship that's accepted
|
|
83
|
+
// by a nominal type system. (That check also rules out enums)
|
|
84
|
+
const nominalCheck = this.isNominalSuperType(a, b);
|
|
85
|
+
if (nominalCheck.success === false) {
|
|
86
|
+
return nominalCheck;
|
|
87
|
+
}
|
|
88
|
+
// At this point, the only thing left to do is recurse into the structs.
|
|
89
|
+
// We used to do that here, but we don't anymore; structs check themselves
|
|
90
|
+
// for structural weakening/strengthening.
|
|
55
91
|
return { success: true };
|
|
56
92
|
}
|
|
57
|
-
|
|
93
|
+
catch (e) {
|
|
94
|
+
return failure(e.message);
|
|
95
|
+
}
|
|
58
96
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Find types A and B in the updated type system, and check whether they have a supertype relationship in the type system
|
|
99
|
+
*/
|
|
100
|
+
isNominalSuperType(a, b) {
|
|
101
|
+
if (a.fqn === undefined) {
|
|
102
|
+
throw new Error(`I was expecting a named type, got '${a.toString()}'`);
|
|
103
|
+
}
|
|
104
|
+
// Named type vs a non-named type
|
|
105
|
+
if (b.fqn === undefined) {
|
|
106
|
+
return failure(`${b.toString()} is not assignable to ${a.toString()}`);
|
|
107
|
+
}
|
|
108
|
+
// Short-circuit of the types are the same name, saves us some lookup
|
|
109
|
+
if (a.fqn === b.fqn) {
|
|
63
110
|
return { success: true };
|
|
64
111
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
112
|
+
// We now need to do subtype analysis on the
|
|
113
|
+
// Find A in B's typesystem, and see if B is a subtype of A'
|
|
114
|
+
const B = this.updatedSystem.tryFindFqn(this.fqnRemapping[b.fqn] ?? b.fqn);
|
|
115
|
+
const A = this.updatedSystem.tryFindFqn(this.fqnRemapping[a.fqn] ?? a.fqn);
|
|
116
|
+
if (!B) {
|
|
117
|
+
return failure(`could not find type ${b.toString()}`);
|
|
118
|
+
}
|
|
119
|
+
if (!A) {
|
|
120
|
+
return failure(`could not find type ${a.toString()}`);
|
|
121
|
+
}
|
|
122
|
+
// If they're enums, they should have been exactly the same (tested above)
|
|
123
|
+
// enums are never subtypes of any other type.
|
|
124
|
+
if (A.isEnumType()) {
|
|
125
|
+
return failure(`${a.toString()} is an enum different from ${b.toString()}`);
|
|
126
|
+
}
|
|
127
|
+
if (B.isEnumType()) {
|
|
128
|
+
return failure(`${b.toString()} is an enum different from ${a.toString()}`);
|
|
129
|
+
}
|
|
130
|
+
if (B.extends(A)) {
|
|
71
131
|
return { success: true };
|
|
72
132
|
}
|
|
73
|
-
return failure(
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Short-circuit of the types are the same name, saves us some lookup
|
|
106
|
-
if (a.fqn === b.fqn) {
|
|
107
|
-
return { success: true };
|
|
108
|
-
}
|
|
109
|
-
// We now need to do subtype analysis on the
|
|
110
|
-
// Find A in B's typesystem, and see if B is a subtype of A'
|
|
111
|
-
const B = updatedSystem.tryFindFqn(b.fqn);
|
|
112
|
-
const A = updatedSystem.tryFindFqn(a.fqn);
|
|
113
|
-
if (!B) {
|
|
114
|
-
return failure(`could not find type ${b.toString()}`);
|
|
115
|
-
}
|
|
116
|
-
if (!A) {
|
|
117
|
-
return failure(`could not find type ${a.toString()}`);
|
|
118
|
-
}
|
|
119
|
-
// If they're enums, they should have been exactly the same (tested above)
|
|
120
|
-
// enums are never subtypes of any other type.
|
|
121
|
-
if (A.isEnumType()) {
|
|
122
|
-
return failure(`${a.toString()} is an enum different from ${b.toString()}`);
|
|
123
|
-
}
|
|
124
|
-
if (B.isEnumType()) {
|
|
125
|
-
return failure(`${b.toString()} is an enum different from ${a.toString()}`);
|
|
126
|
-
}
|
|
127
|
-
if (B.extends(A)) {
|
|
128
|
-
return { success: true };
|
|
129
|
-
}
|
|
130
|
-
return failure(`${b.toString()} does not extend ${a.toString()}`);
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Is struct A a structural supertype of struct B?
|
|
134
|
-
*
|
|
135
|
-
* Trying to answer the question, is this assignment legal for all values
|
|
136
|
-
* of `expr in B`.
|
|
137
|
-
*
|
|
138
|
-
* ```ts
|
|
139
|
-
* const var: A = expr as B;
|
|
140
|
-
* ```
|
|
141
|
-
*
|
|
142
|
-
* A is a structural supertype of B if all required members of A are also
|
|
143
|
-
* required in B, and of a compatible type.
|
|
144
|
-
*
|
|
145
|
-
* Nullable members of A must either not exist in B, or be of a compatible
|
|
146
|
-
* type.
|
|
147
|
-
*/
|
|
148
|
-
function isStructuralSuperType(a, b, updatedSystem) {
|
|
149
|
-
// We know all members can only be properties, so that makes it easier.
|
|
150
|
-
const bProps = b.getProperties(true);
|
|
151
|
-
// Use timing words in error message to make it more understandable
|
|
152
|
-
const formerly = b.system === updatedSystem ? 'formerly' : 'newly';
|
|
153
|
-
const is = b.system === updatedSystem ? 'is' : 'used to be';
|
|
154
|
-
const removed = b.system === updatedSystem ? 'removed' : 'added';
|
|
155
|
-
for (const [name, aProp] of Object.entries(a.getProperties(true))) {
|
|
156
|
-
const bProp = bProps[name];
|
|
157
|
-
if (aProp.optional) {
|
|
158
|
-
// Optional field, only requirement is that IF it exists, the type must match.
|
|
159
|
-
if (!bProp) {
|
|
160
|
-
continue;
|
|
133
|
+
return failure(`${b.toString()} does not extend ${a.toString()}`);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Is struct A a structural supertype of struct B?
|
|
137
|
+
*
|
|
138
|
+
* Trying to answer the question, is this assignment legal for all values
|
|
139
|
+
* of `expr in B`.
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* const var: A = expr as B;
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* A is a structural supertype of B if all required members of A are also
|
|
146
|
+
* required in B, and of a compatible type.
|
|
147
|
+
*
|
|
148
|
+
* Nullable members of A must either not exist in B, or be of a compatible
|
|
149
|
+
* type.
|
|
150
|
+
*/
|
|
151
|
+
isStructuralSuperType(a, b) {
|
|
152
|
+
// We know all members can only be properties, so that makes it easier.
|
|
153
|
+
const bProps = b.getProperties(true);
|
|
154
|
+
// Use timing words in error message to make it more understandable
|
|
155
|
+
const formerly = b.system === this.updatedSystem ? 'formerly' : 'newly';
|
|
156
|
+
const is = b.system === this.updatedSystem ? 'is' : 'used to be';
|
|
157
|
+
const removed = b.system === this.updatedSystem ? 'removed' : 'added';
|
|
158
|
+
for (const [name, aProp] of Object.entries(a.getProperties(true))) {
|
|
159
|
+
const bProp = bProps[name];
|
|
160
|
+
if (aProp.optional) {
|
|
161
|
+
// Optional field, only requirement is that IF it exists, the type must match.
|
|
162
|
+
if (!bProp) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
161
165
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
else {
|
|
167
|
+
if (!bProp) {
|
|
168
|
+
return failure(`${formerly} required property '${name}' ${removed}`);
|
|
169
|
+
}
|
|
170
|
+
if (bProp.optional) {
|
|
171
|
+
return failure(`${formerly} required property '${name}' ${is} optional`);
|
|
172
|
+
}
|
|
166
173
|
}
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
const ana = this.isSuperType(aProp.type, bProp.type);
|
|
175
|
+
if (!ana.success) {
|
|
176
|
+
return failure(`property ${name}`, ...ana.reasons);
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
|
-
|
|
172
|
-
if (!ana.success) {
|
|
173
|
-
return failure(`property ${name}`, ...ana.reasons);
|
|
174
|
-
}
|
|
179
|
+
return { success: true };
|
|
175
180
|
}
|
|
176
|
-
return { success: true };
|
|
177
181
|
}
|
|
182
|
+
exports.TypeAnalysis = TypeAnalysis;
|
|
178
183
|
function failure(...reasons) {
|
|
179
184
|
return { success: false, reasons };
|
|
180
185
|
}
|
package/lib/type-comparison.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export declare class AssemblyComparison {
|
|
|
24
24
|
* Based on a JSII TypeReference, return all reachable ComparableType<> objects.
|
|
25
25
|
*/
|
|
26
26
|
typesIn(ref: reflect.TypeReference): Array<ComparableType<any>>;
|
|
27
|
+
/**
|
|
28
|
+
* Return the type's FQN, running it through the translation table if present.
|
|
29
|
+
*/
|
|
30
|
+
private resolveFqn;
|
|
27
31
|
/**
|
|
28
32
|
* All ComparableType<>s
|
|
29
33
|
*/
|
|
@@ -44,10 +48,12 @@ export declare abstract class ComparableType<T> {
|
|
|
44
48
|
protected readonly assemblyComparison: AssemblyComparison;
|
|
45
49
|
protected readonly oldType: T;
|
|
46
50
|
protected readonly newType: T;
|
|
51
|
+
protected readonly displayFqn: string;
|
|
47
52
|
private static readonly recursionBreaker;
|
|
48
53
|
private readonly _inputTypeReasons;
|
|
49
54
|
private readonly _outputTypeReasons;
|
|
50
|
-
constructor(assemblyComparison: AssemblyComparison, oldType: T, newType: T);
|
|
55
|
+
constructor(assemblyComparison: AssemblyComparison, oldType: T, newType: T, displayFqn: string);
|
|
56
|
+
get fqnRemapping(): Record<string, string>;
|
|
51
57
|
/**
|
|
52
58
|
* Does this type occur in an input role?
|
|
53
59
|
*/
|
package/lib/type-comparison.js
CHANGED
|
@@ -33,7 +33,8 @@ class AssemblyComparison {
|
|
|
33
33
|
load(original, updated) {
|
|
34
34
|
/* eslint-disable prettier/prettier */
|
|
35
35
|
for (const [origClass, updatedClass] of this.typePairs(original.allClasses, updated)) {
|
|
36
|
-
|
|
36
|
+
const { fqn, displayFqn } = this.resolveFqn(origClass);
|
|
37
|
+
this.types.set(fqn, new ComparableClassType(this, origClass, updatedClass, displayFqn));
|
|
37
38
|
}
|
|
38
39
|
for (const [origIface, updatedIface] of this.typePairs(original.allInterfaces, updated)) {
|
|
39
40
|
if (origIface.datatype !== updatedIface.datatype) {
|
|
@@ -44,12 +45,14 @@ class AssemblyComparison {
|
|
|
44
45
|
});
|
|
45
46
|
continue;
|
|
46
47
|
}
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const { fqn, displayFqn } = this.resolveFqn(origIface);
|
|
49
|
+
this.types.set(fqn, origIface.datatype
|
|
50
|
+
? new ComparableStructType(this, origIface, updatedIface, displayFqn)
|
|
51
|
+
: new ComparableInterfaceType(this, origIface, updatedIface, displayFqn));
|
|
50
52
|
}
|
|
51
53
|
for (const [origEnum, updatedEnum] of this.typePairs(original.allEnums, updated)) {
|
|
52
|
-
|
|
54
|
+
const { fqn, displayFqn } = this.resolveFqn(origEnum);
|
|
55
|
+
this.types.set(fqn, new ComparableEnumType(this, origEnum, updatedEnum, displayFqn));
|
|
53
56
|
}
|
|
54
57
|
/* eslint-enable prettier/prettier */
|
|
55
58
|
}
|
|
@@ -67,13 +70,24 @@ class AssemblyComparison {
|
|
|
67
70
|
typesIn(ref) {
|
|
68
71
|
const ret = new Array();
|
|
69
72
|
for (const fqn of fqnsFrom(ref)) {
|
|
70
|
-
const t = this.types.get(fqn);
|
|
73
|
+
const t = this.types.get(this.resolveFqn(fqn).fqn);
|
|
71
74
|
if (t) {
|
|
72
75
|
ret.push(t);
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
return ret;
|
|
76
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Return the type's FQN, running it through the translation table if present.
|
|
82
|
+
*/
|
|
83
|
+
resolveFqn(x) {
|
|
84
|
+
const fqn = typeof x === 'string' ? x : x.fqn;
|
|
85
|
+
const finalFqn = this.options.fqnRemapping?.[fqn] ?? fqn;
|
|
86
|
+
if (fqn !== finalFqn) {
|
|
87
|
+
return { fqn: finalFqn, displayFqn: `${fqn} -> ${finalFqn}` };
|
|
88
|
+
}
|
|
89
|
+
return { fqn, displayFqn: fqn };
|
|
90
|
+
}
|
|
77
91
|
/**
|
|
78
92
|
* All ComparableType<>s
|
|
79
93
|
*/
|
|
@@ -85,8 +99,9 @@ class AssemblyComparison {
|
|
|
85
99
|
*/
|
|
86
100
|
*typePairs(xs, updatedAssembly) {
|
|
87
101
|
for (const origType of xs) {
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
const { fqn, displayFqn } = this.resolveFqn(origType);
|
|
103
|
+
LOG.trace(displayFqn);
|
|
104
|
+
const updatedType = updatedAssembly.tryFindType(fqn);
|
|
90
105
|
if (!updatedType) {
|
|
91
106
|
this.mismatches.report({
|
|
92
107
|
ruleKey: 'removed',
|
|
@@ -116,13 +131,17 @@ exports.AssemblyComparison = AssemblyComparison;
|
|
|
116
131
|
* object.
|
|
117
132
|
*/
|
|
118
133
|
class ComparableType {
|
|
119
|
-
constructor(assemblyComparison, oldType, newType) {
|
|
134
|
+
constructor(assemblyComparison, oldType, newType, displayFqn) {
|
|
120
135
|
this.assemblyComparison = assemblyComparison;
|
|
121
136
|
this.oldType = oldType;
|
|
122
137
|
this.newType = newType;
|
|
138
|
+
this.displayFqn = displayFqn;
|
|
123
139
|
this._inputTypeReasons = new Array();
|
|
124
140
|
this._outputTypeReasons = new Array();
|
|
125
141
|
}
|
|
142
|
+
get fqnRemapping() {
|
|
143
|
+
return this.assemblyComparison.options.fqnRemapping ?? {};
|
|
144
|
+
}
|
|
126
145
|
/**
|
|
127
146
|
* Does this type occur in an input role?
|
|
128
147
|
*/
|
|
@@ -208,9 +227,9 @@ class ComparableReferenceType extends ComparableType {
|
|
|
208
227
|
* Compare members of the reference types
|
|
209
228
|
*/
|
|
210
229
|
compare() {
|
|
211
|
-
LOG.debug(`Reference type ${this.
|
|
230
|
+
LOG.debug(`Reference type ${this.displayFqn}`);
|
|
212
231
|
(0, stability_1.validateStabilities)(this.oldType, this.newType, this.mismatches);
|
|
213
|
-
(0, validations_1.validateBaseTypeAssignability)(this.oldType, this.newType, this.mismatches);
|
|
232
|
+
(0, validations_1.validateBaseTypeAssignability)(this.oldType, this.newType, this.fqnRemapping, this.mismatches);
|
|
214
233
|
(0, validations_1.validateSubclassableNotRemoved)(this.oldType, this.newType, this.mismatches);
|
|
215
234
|
if (this.subclassableType) {
|
|
216
235
|
(0, validations_1.validateNoNewAbstractMembers)(this.oldType, this.newType, this.mismatches);
|
|
@@ -249,7 +268,7 @@ class ComparableReferenceType extends ComparableType {
|
|
|
249
268
|
(0, validations_1.validateReturnTypeSame)(original, updated, this.mismatches.withMotivation('type is @subclassable'));
|
|
250
269
|
}
|
|
251
270
|
else {
|
|
252
|
-
(0, validations_1.validateReturnTypeNotWeakened)(original, updated, this.mismatches);
|
|
271
|
+
(0, validations_1.validateReturnTypeNotWeakened)(original, updated, this.fqnRemapping, this.mismatches);
|
|
253
272
|
}
|
|
254
273
|
this.validateCallable(original, updated);
|
|
255
274
|
}
|
|
@@ -265,7 +284,7 @@ class ComparableReferenceType extends ComparableType {
|
|
|
265
284
|
(0, validations_1.validateParameterTypeSame)(original, oldParam, newParam, this.mismatches.withMotivation('type is @subclassable'));
|
|
266
285
|
}
|
|
267
286
|
else {
|
|
268
|
-
(0, validations_1.validateParameterTypeWeakened)(original, oldParam, newParam, this.mismatches);
|
|
287
|
+
(0, validations_1.validateParameterTypeWeakened)(original, oldParam, newParam, this.fqnRemapping, this.mismatches);
|
|
269
288
|
}
|
|
270
289
|
});
|
|
271
290
|
(0, validations_1.validateNoNewRequiredParams)(original, updated, this.mismatches);
|
|
@@ -298,7 +317,7 @@ class ComparableReferenceType extends ComparableType {
|
|
|
298
317
|
(0, validations_1.validatePropertyTypeSame)(original, updated, this.mismatches.withMotivation('mutable property cannot change type'));
|
|
299
318
|
}
|
|
300
319
|
else {
|
|
301
|
-
(0, validations_1.validatePropertyTypeNotWeakened)(original, updated, this.mismatches);
|
|
320
|
+
(0, validations_1.validatePropertyTypeNotWeakened)(original, updated, this.fqnRemapping, this.mismatches);
|
|
302
321
|
}
|
|
303
322
|
}
|
|
304
323
|
/**
|
|
@@ -318,7 +337,7 @@ class ComparableClassType extends ComparableReferenceType {
|
|
|
318
337
|
(0, validations_1.validateNotMadeAbstract)(this.oldType, this.newType, this.mismatches);
|
|
319
338
|
// JSII assembler has already taken care of inheritance here
|
|
320
339
|
if (this.oldType.initializer && this.newType.initializer) {
|
|
321
|
-
(0, validations_1.validateMethodCompatible)(this.oldType.initializer, this.newType.initializer, this.mismatches);
|
|
340
|
+
(0, validations_1.validateMethodCompatible)(this.oldType.initializer, this.newType.initializer, this.fqnRemapping, this.mismatches);
|
|
322
341
|
}
|
|
323
342
|
}
|
|
324
343
|
/**
|
|
@@ -348,9 +367,9 @@ exports.ComparableInterfaceType = ComparableInterfaceType;
|
|
|
348
367
|
*/
|
|
349
368
|
class ComparableStructType extends ComparableType {
|
|
350
369
|
compare() {
|
|
351
|
-
LOG.debug(`Struct type ${this.
|
|
370
|
+
LOG.debug(`Struct type ${this.displayFqn}`);
|
|
352
371
|
(0, stability_1.validateStabilities)(this.oldType, this.newType, this.mismatches);
|
|
353
|
-
(0, validations_1.validateBaseTypeAssignability)(this.oldType, this.newType, this.mismatches);
|
|
372
|
+
(0, validations_1.validateBaseTypeAssignability)(this.oldType, this.newType, this.fqnRemapping, this.mismatches);
|
|
354
373
|
this.validateNoPropertiesRemoved();
|
|
355
374
|
if (this.inputType) {
|
|
356
375
|
// If the struct is written, it can't be strengthened (ex: can't change an optional property to required)
|
|
@@ -409,7 +428,7 @@ class ComparableStructType extends ComparableType {
|
|
|
409
428
|
}
|
|
410
429
|
isStructuralSuperType(a, b) {
|
|
411
430
|
try {
|
|
412
|
-
return (
|
|
431
|
+
return new type_analysis_1.TypeAnalysis(this.newType.system, this.fqnRemapping).isStructuralSuperType(a, b);
|
|
413
432
|
}
|
|
414
433
|
catch (e) {
|
|
415
434
|
// We might get an exception if the type is supposed to come from a different
|
|
@@ -427,7 +446,7 @@ class ComparableEnumType extends ComparableType {
|
|
|
427
446
|
* Perform comparisons on enum members
|
|
428
447
|
*/
|
|
429
448
|
compare() {
|
|
430
|
-
LOG.debug(`Enum type ${this.
|
|
449
|
+
LOG.debug(`Enum type ${this.displayFqn}`);
|
|
431
450
|
(0, stability_1.validateStabilities)(this.oldType, this.newType, this.mismatches);
|
|
432
451
|
(0, validations_1.validateExistingMembers)(this.oldType, this.newType, this.mismatches, (oldMember, newMember) => {
|
|
433
452
|
(0, stability_1.validateStabilities)(oldMember, newMember, this.mismatches);
|
package/lib/types.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface ComparisonOptions {
|
|
|
7
7
|
* @default Treat as stable
|
|
8
8
|
*/
|
|
9
9
|
defaultExperimental?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Mapping old FQNs to new ones
|
|
12
|
+
*/
|
|
13
|
+
fqnRemapping?: Record<string, string>;
|
|
10
14
|
}
|
|
11
15
|
export interface ComparisonContext extends ComparisonOptions {
|
|
12
16
|
/**
|
package/lib/validations.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { IReport } from './types';
|
|
|
11
11
|
*
|
|
12
12
|
* Where CLASS ≤: BASE.
|
|
13
13
|
*/
|
|
14
|
-
export declare function validateBaseTypeAssignability<T extends reflect.ReferenceType>(original: T, updated: T, mismatches: IReport): void;
|
|
14
|
+
export declare function validateBaseTypeAssignability<T extends reflect.ReferenceType>(original: T, updated: T, fqnRemapping: Record<string, string>, mismatches: IReport): void;
|
|
15
15
|
/**
|
|
16
16
|
* The updated type has not been newly made abstract
|
|
17
17
|
*
|
|
@@ -58,7 +58,7 @@ export declare function validateNoNewAbstractMembers<T extends reflect.Reference
|
|
|
58
58
|
*
|
|
59
59
|
* Where RETURN_TYPE(method) ≤: T.
|
|
60
60
|
*/
|
|
61
|
-
export declare function validateReturnTypeNotWeakened(original: reflect.Method, updated: reflect.Method, mismatches: IReport): void;
|
|
61
|
+
export declare function validateReturnTypeNotWeakened(original: reflect.Method, updated: reflect.Method, fqnRemapping: Record<string, string>, mismatches: IReport): void;
|
|
62
62
|
/**
|
|
63
63
|
* Validate that a method return type is the exact same
|
|
64
64
|
*
|
|
@@ -76,7 +76,7 @@ export declare function validateReturnTypeSame(original: reflect.Method, updated
|
|
|
76
76
|
*
|
|
77
77
|
* Where RETURN_TYPE(prop) ≤: T.
|
|
78
78
|
*/
|
|
79
|
-
export declare function validatePropertyTypeNotWeakened(original: reflect.Property, updated: reflect.Property, mismatches: IReport): void;
|
|
79
|
+
export declare function validatePropertyTypeNotWeakened(original: reflect.Property, updated: reflect.Property, fqnRemapping: Record<string, string>, mismatches: IReport): void;
|
|
80
80
|
/**
|
|
81
81
|
* Validate that a property type is the exact same
|
|
82
82
|
*
|
|
@@ -96,7 +96,7 @@ export declare function validatePropertyTypeSame(original: reflect.Property, upd
|
|
|
96
96
|
*
|
|
97
97
|
* Where T ≤: U.
|
|
98
98
|
*/
|
|
99
|
-
export declare function validateParameterTypeWeakened(method: reflect.Method | reflect.Initializer, original: reflect.Parameter, updated: reflect.Parameter, mismatches: IReport): void;
|
|
99
|
+
export declare function validateParameterTypeWeakened(method: reflect.Method | reflect.Initializer, original: reflect.Parameter, updated: reflect.Parameter, fqnRemapping: Record<string, string>, mismatches: IReport): void;
|
|
100
100
|
/**
|
|
101
101
|
* Validate that a method parameter type is the exact same
|
|
102
102
|
*
|
|
@@ -127,7 +127,7 @@ export declare function validateExistingParams<T extends reflect.Initializer | r
|
|
|
127
127
|
* (Not too few arguments)
|
|
128
128
|
*/
|
|
129
129
|
export declare function validateNoNewRequiredParams<T extends reflect.Initializer | reflect.Method>(original: T, updated: T, mismatches: IReport): void;
|
|
130
|
-
export declare function validateMethodCompatible<T extends reflect.Method | reflect.Initializer>(original: T, updated: T, mismatches: IReport): void;
|
|
130
|
+
export declare function validateMethodCompatible<T extends reflect.Method | reflect.Initializer>(original: T, updated: T, fqnRemapping: Record<string, string>, mismatches: IReport): void;
|
|
131
131
|
/**
|
|
132
132
|
* Check if a class/interface has been marked as @subclassable
|
|
133
133
|
*/
|
package/lib/validations.js
CHANGED
|
@@ -36,8 +36,8 @@ const LOG = log4js.getLogger('jsii-diff');
|
|
|
36
36
|
*
|
|
37
37
|
* Where CLASS ≤: BASE.
|
|
38
38
|
*/
|
|
39
|
-
function validateBaseTypeAssignability(original, updated, mismatches) {
|
|
40
|
-
const ana = assignableToAllBaseTypes(original, updated);
|
|
39
|
+
function validateBaseTypeAssignability(original, updated, fqnRemapping, mismatches) {
|
|
40
|
+
const ana = assignableToAllBaseTypes(original, updated, fqnRemapping);
|
|
41
41
|
if (!ana.success) {
|
|
42
42
|
mismatches.report({
|
|
43
43
|
ruleKey: 'base-types',
|
|
@@ -146,8 +146,8 @@ function validateNoNewAbstractMembers(original, updated, mismatches) {
|
|
|
146
146
|
*
|
|
147
147
|
* Where RETURN_TYPE(method) ≤: T.
|
|
148
148
|
*/
|
|
149
|
-
function validateReturnTypeNotWeakened(original, updated, mismatches) {
|
|
150
|
-
const retAna = isCompatibleReturnType(original.returns, updated.returns);
|
|
149
|
+
function validateReturnTypeNotWeakened(original, updated, fqnRemapping, mismatches) {
|
|
150
|
+
const retAna = isCompatibleReturnType(original.returns, updated.returns, fqnRemapping);
|
|
151
151
|
if (!retAna.success) {
|
|
152
152
|
mismatches.report({
|
|
153
153
|
ruleKey: 'change-return-type',
|
|
@@ -183,8 +183,8 @@ function validateReturnTypeSame(original, updated, mismatches) {
|
|
|
183
183
|
*
|
|
184
184
|
* Where RETURN_TYPE(prop) ≤: T.
|
|
185
185
|
*/
|
|
186
|
-
function validatePropertyTypeNotWeakened(original, updated, mismatches) {
|
|
187
|
-
const ana = isCompatibleReturnType(original, updated);
|
|
186
|
+
function validatePropertyTypeNotWeakened(original, updated, fqnRemapping, mismatches) {
|
|
187
|
+
const ana = isCompatibleReturnType(original, updated, fqnRemapping);
|
|
188
188
|
if (!ana.success) {
|
|
189
189
|
mismatches.report({
|
|
190
190
|
ruleKey: 'changed-type',
|
|
@@ -222,8 +222,8 @@ function validatePropertyTypeSame(original, updated, mismatches) {
|
|
|
222
222
|
*
|
|
223
223
|
* Where T ≤: U.
|
|
224
224
|
*/
|
|
225
|
-
function validateParameterTypeWeakened(method, original, updated, mismatches) {
|
|
226
|
-
const argAna = isCompatibleArgumentType(original.type, updated.type);
|
|
225
|
+
function validateParameterTypeWeakened(method, original, updated, fqnRemapping, mismatches) {
|
|
226
|
+
const argAna = isCompatibleArgumentType(original.type, updated.type, fqnRemapping);
|
|
227
227
|
if (!argAna.success) {
|
|
228
228
|
mismatches.report({
|
|
229
229
|
ruleKey: 'incompatible-argument',
|
|
@@ -306,18 +306,18 @@ function validateNoNewRequiredParams(original, updated, mismatches) {
|
|
|
306
306
|
}
|
|
307
307
|
});
|
|
308
308
|
}
|
|
309
|
-
function validateMethodCompatible(original, updated, mismatches) {
|
|
309
|
+
function validateMethodCompatible(original, updated, fqnRemapping, mismatches) {
|
|
310
310
|
(0, stability_1.validateStabilities)(original, updated, mismatches);
|
|
311
311
|
// Type guards on original are duplicated on updated to help tsc... They are required to be the same type by the declaration.
|
|
312
312
|
if (reflect.isMethod(original) && reflect.isMethod(updated)) {
|
|
313
313
|
validateStaticSame(original, updated, mismatches);
|
|
314
314
|
validateAsyncSame(original, updated, mismatches);
|
|
315
|
-
validateReturnTypeNotWeakened(original, updated, mismatches);
|
|
315
|
+
validateReturnTypeNotWeakened(original, updated, fqnRemapping, mismatches);
|
|
316
316
|
}
|
|
317
317
|
validateNotMadeNonVariadic(original, updated, mismatches);
|
|
318
318
|
// Check that every original parameter can still be mapped to a parameter in the updated method
|
|
319
319
|
validateExistingParams(original, updated, mismatches, (oldParam, newParam) => {
|
|
320
|
-
validateParameterTypeWeakened(original, oldParam, newParam, mismatches);
|
|
320
|
+
validateParameterTypeWeakened(original, oldParam, newParam, fqnRemapping, mismatches);
|
|
321
321
|
});
|
|
322
322
|
validateNoNewRequiredParams(original, updated, mismatches);
|
|
323
323
|
}
|
|
@@ -394,7 +394,7 @@ function* memberPairs(origClass, xs, updatedClass, mismatches) {
|
|
|
394
394
|
*
|
|
395
395
|
* Strengthening output values is allowed!
|
|
396
396
|
*/
|
|
397
|
-
function isCompatibleReturnType(original, updated) {
|
|
397
|
+
function isCompatibleReturnType(original, updated, fqnRemapping) {
|
|
398
398
|
if (original.type.void) {
|
|
399
399
|
return { success: true };
|
|
400
400
|
} // If we didn't use to return anything, returning something now is fine
|
|
@@ -404,16 +404,16 @@ function isCompatibleReturnType(original, updated) {
|
|
|
404
404
|
if (!original.optional && updated.optional) {
|
|
405
405
|
return { success: false, reasons: ['output type is now optional'] };
|
|
406
406
|
}
|
|
407
|
-
return (
|
|
407
|
+
return new type_analysis_1.TypeAnalysis(updated.system, fqnRemapping).isSuperType(original.type, updated.type);
|
|
408
408
|
}
|
|
409
409
|
/**
|
|
410
410
|
* Whether we are weakening the pre (input type of a method)
|
|
411
411
|
*
|
|
412
412
|
* Weakening preconditions is allowed!
|
|
413
413
|
*/
|
|
414
|
-
function isCompatibleArgumentType(original, updated) {
|
|
414
|
+
function isCompatibleArgumentType(original, updated, fqnRemapping) {
|
|
415
415
|
// Input can never be void, so no need to check
|
|
416
|
-
return (
|
|
416
|
+
return new type_analysis_1.TypeAnalysis(updated.system, fqnRemapping).isSuperType(updated, original);
|
|
417
417
|
}
|
|
418
418
|
/**
|
|
419
419
|
* Verify assignability to supertypes
|
|
@@ -428,9 +428,9 @@ function isCompatibleArgumentType(original, updated) {
|
|
|
428
428
|
* B an updated type B' needs to exist in the new assembly which is
|
|
429
429
|
* still a supertype of T'.
|
|
430
430
|
*/
|
|
431
|
-
function assignableToAllBaseTypes(original, updated) {
|
|
431
|
+
function assignableToAllBaseTypes(original, updated, fqnRemapping) {
|
|
432
432
|
for (const B of baseTypes(original)) {
|
|
433
|
-
const result = (
|
|
433
|
+
const result = new type_analysis_1.TypeAnalysis(updated.system, fqnRemapping).isNominalSuperType(B.reference, updated.reference);
|
|
434
434
|
if (!result.success) {
|
|
435
435
|
return result;
|
|
436
436
|
}
|
package/lib/version.d.ts
CHANGED
package/lib/version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Generated at 2025-
|
|
2
|
+
// Generated at 2025-11-24T11:53:14Z by generate.sh
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.VERSION = void 0;
|
|
5
5
|
/** The qualified version number for this JSII compiler. */
|
|
6
|
-
exports.VERSION = '1.
|
|
6
|
+
exports.VERSION = '1.120.0 (build 192dc88)';
|
|
7
7
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsii-diff",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.120.0",
|
|
4
4
|
"description": "Assembly comparison for jsii",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"package": "package-js"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jsii/check-node": "1.
|
|
37
|
-
"@jsii/spec": "1.
|
|
36
|
+
"@jsii/check-node": "1.120.0",
|
|
37
|
+
"@jsii/spec": "1.120.0",
|
|
38
38
|
"fs-extra": "^10.1.0",
|
|
39
|
-
"jsii-reflect": "^1.
|
|
39
|
+
"jsii-reflect": "^1.120.0",
|
|
40
40
|
"log4js": "^6.9.1",
|
|
41
41
|
"yargs": "^17.7.2"
|
|
42
42
|
},
|
|
@@ -46,6 +46,6 @@
|
|
|
46
46
|
"@types/yargs": "^17.0.33",
|
|
47
47
|
"jest-expect-message": "^1.1.3",
|
|
48
48
|
"jsii": "^5.9.10",
|
|
49
|
-
"jsii-build-tools": "^1.
|
|
49
|
+
"jsii-build-tools": "^1.120.0"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/test/classes.test.js
CHANGED
|
@@ -547,4 +547,14 @@ test('will find mismatches in submodules', () => (0, util_1.expectError)(/number
|
|
|
547
547
|
'index.ts': 'export * as submodule from "./subdir"',
|
|
548
548
|
'subdir/index.ts': 'export class Foo { public static readonly PROP = 42; }',
|
|
549
549
|
}));
|
|
550
|
+
// ----------------------------------------------------------------------
|
|
551
|
+
test('allow remapping of FQNs', () => {
|
|
552
|
+
const original = `export class Foo1 { }`;
|
|
553
|
+
const updated = `export class Foo2 { }`;
|
|
554
|
+
// This should give no error because we remapped Foo1 to Foo2
|
|
555
|
+
const mms = (0, util_1.compare)(original, updated, {
|
|
556
|
+
fqnRemapping: { 'testpkg.Foo1': 'testpkg.Foo2' },
|
|
557
|
+
});
|
|
558
|
+
expect(Array.from(mms.messages())).toEqual([]);
|
|
559
|
+
});
|
|
550
560
|
//# sourceMappingURL=classes.test.js.map
|
package/test/util.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MultipleSourceFiles } from 'jsii';
|
|
2
|
-
import { Mismatches } from '../lib/types';
|
|
2
|
+
import { ComparisonOptions, Mismatches } from '../lib/types';
|
|
3
3
|
export declare function expectNoError(original: string | MultipleSourceFiles, updated: string | MultipleSourceFiles): void;
|
|
4
4
|
export declare function expectError(error: RegExp | undefined, original: string | MultipleSourceFiles, updated: string | MultipleSourceFiles): void;
|
|
5
|
-
export declare function compare(original: string | MultipleSourceFiles, updated: string | MultipleSourceFiles): Mismatches;
|
|
5
|
+
export declare function compare(original: string | MultipleSourceFiles, updated: string | MultipleSourceFiles, options?: ComparisonOptions): Mismatches;
|
|
6
6
|
//# sourceMappingURL=util.d.ts.map
|
package/test/util.js
CHANGED
|
@@ -25,13 +25,13 @@ function expectError(error, original, updated) {
|
|
|
25
25
|
expect(msgs.join(',')).toMatch(error);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
function compare(original, updated) {
|
|
28
|
+
function compare(original, updated, options = {}) {
|
|
29
29
|
const ass1 = (0, jsii_1.sourceToAssemblyHelper)(original);
|
|
30
30
|
const ts1 = new reflect.TypeSystem();
|
|
31
31
|
const originalAssembly = ts1.addAssembly(new reflect.Assembly(ts1, ass1));
|
|
32
32
|
const ass2 = (0, jsii_1.sourceToAssemblyHelper)(updated);
|
|
33
33
|
const ts2 = new reflect.TypeSystem();
|
|
34
34
|
const updatedAssembly = ts2.addAssembly(new reflect.Assembly(ts2, ass2));
|
|
35
|
-
return (0, lib_1.compareAssemblies)(originalAssembly, updatedAssembly);
|
|
35
|
+
return (0, lib_1.compareAssemblies)(originalAssembly, updatedAssembly, options);
|
|
36
36
|
}
|
|
37
37
|
//# sourceMappingURL=util.js.map
|