Skip to content

Commit 8bcfd51

Browse files
author
Kamil Sobol
authored
cover class inheritance in api check (#876)
* cover class inheritance in api check * nit * pr feedback
1 parent 28343b2 commit 8bcfd51

File tree

9 files changed

+176
-2
lines changed

9 files changed

+176
-2
lines changed

scripts/components/api-changes-validator/api_usage_generator.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,43 @@ class SomeAbstractClass2DerivedUsageClass<T1 extends SomeClass1, T2, T3, T4, T5,
236236
constructor(param1: T1, param2?: T2) {
237237
super(param1, param2);
238238
}
239+
}
240+
`,
241+
},
242+
{
243+
description: 'generates class inheritance usage',
244+
apiReportCode: `
245+
export type SomeType1 = {
246+
};
247+
export type SomeType2<T1, T2> = {
248+
};
249+
export abstract class SomeAbstractClass<T1, T2> {
250+
}
251+
export class SomeClass<T1, T2, T3, T4> extends SomeAbstractClass<T1, T2> implements SomeType1, SomeType2<T3, T4>{
252+
}
253+
`,
254+
expectedApiUsage: `
255+
import { SomeType1 } from 'samplePackageName';
256+
import { SomeType2 } from 'samplePackageName';
257+
import { SomeAbstractClass } from 'samplePackageName';
258+
import { SomeClass } from 'samplePackageName';
259+
260+
type SomeType1Baseline = {
261+
}
262+
const someType1UsageFunction = (someType1FunctionParameter: SomeType1Baseline) => {
263+
const someType1: SomeType1 = someType1FunctionParameter;
264+
}
265+
266+
type SomeType2Baseline<T1, T2> = {
267+
}
268+
const someType2UsageFunction = <T1, T2>(someType2FunctionParameter: SomeType2Baseline<T1, T2>) => {
269+
const someType2: SomeType2<T1, T2> = someType2FunctionParameter;
270+
}
271+
272+
const someClassInheritanceUsageFunction = <T1, T2, T3, T4>(someClassInheritanceUsageFunctionParameter: SomeClass<T1, T2, T3, T4>) => {
273+
const superTypeUsageConst0: SomeAbstractClass<T1, T2> = someClassInheritanceUsageFunctionParameter;
274+
const superTypeUsageConst1: SomeType1 = someClassInheritanceUsageFunctionParameter;
275+
const superTypeUsageConst2: SomeType2<T3, T4> = someClassInheritanceUsageFunctionParameter;
239276
}
240277
`,
241278
},

scripts/components/api-changes-validator/api_usage_statements_generators.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,6 @@ export class EnumUsageStatementsGenerator implements UsageStatementsGenerator {
189189
* if they have private members, see https://github.com/microsoft/TypeScript/issues/53558.
190190
* Therefore strategy for classes is different. Instead of testing assignability
191191
* like we do for types we generate usage of it's members.
192-
*
193-
* TODO This covers properties and methods now, we should cover constructor and inheritance
194192
*/
195193
export class ClassUsageStatementsGenerator implements UsageStatementsGenerator {
196194
/**
@@ -230,6 +228,14 @@ export class ClassUsageStatementsGenerator implements UsageStatementsGenerator {
230228
}
231229
}
232230

231+
if (this.classDeclaration.heritageClauses) {
232+
usageStatement +=
233+
new ClassInheritanceUsageStatementsGenerator(
234+
this.classDeclaration,
235+
this.classDeclaration.heritageClauses
236+
).generate().usageStatement ?? '';
237+
}
238+
233239
if (usageStatement) {
234240
usageStatement += EOL;
235241
}
@@ -415,6 +421,53 @@ class ClassConstructorUsageStatementsGenerator
415421
};
416422
}
417423

424+
/**
425+
* Generates usage snippets for class inheritance.
426+
* Generated snippets attempt to use a reference typed with class (provided via usage function parameter)
427+
* and assign it to local constant that is typed with super type from extend or implement clauses.
428+
*/
429+
class ClassInheritanceUsageStatementsGenerator
430+
implements UsageStatementsGenerator
431+
{
432+
constructor(
433+
private readonly classDeclaration: ts.ClassDeclaration,
434+
private readonly heritageClauses: ts.NodeArray<ts.HeritageClause>
435+
) {}
436+
437+
generate = (): UsageStatements => {
438+
const className = this.classDeclaration.name?.getText();
439+
if (!className) {
440+
throw new Error('Class name is missing');
441+
}
442+
const usageFunctionName = toLowerCamelCase(
443+
`${className}InheritanceUsageFunction`
444+
);
445+
const usageFunctionParameterName = `${usageFunctionName}Parameter`;
446+
const genericTypeParametersDeclaration =
447+
new GenericTypeParameterDeclarationUsageStatementsGenerator(
448+
this.classDeclaration.typeParameters
449+
).generate().usageStatement ?? '';
450+
const genericTypeParameters =
451+
new GenericTypeParameterUsageStatementsGenerator(
452+
this.classDeclaration.typeParameters
453+
).generate().usageStatement ?? '';
454+
455+
const superTypeUsageStatements = this.heritageClauses
456+
.flatMap((clause) => clause.types)
457+
.map((superType, index) => {
458+
return `const superTypeUsageConst${index}: ${superType.getText()} = ${usageFunctionParameterName};`;
459+
});
460+
461+
let usageStatement = '';
462+
usageStatement += `const ${usageFunctionName} = ${genericTypeParametersDeclaration}(${usageFunctionParameterName}: ${className}${genericTypeParameters}) => {${EOL}`;
463+
for (const superTypeUsageStatement of superTypeUsageStatements) {
464+
usageStatement += `${indent(superTypeUsageStatement)}${EOL}`;
465+
}
466+
usageStatement += `}${EOL}`;
467+
return { usageStatement };
468+
};
469+
}
470+
418471
/**
419472
* Generates usage of a variable/const/arrow function declared as top level export
420473
* , i.e. 'const someConst = ...`;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
```ts
2+
export type SomeType1 = {
3+
someProperty1: string;
4+
};
5+
6+
export type SomeType2 = {
7+
someProperty2: string;
8+
someProperty3: string;
9+
};
10+
11+
export abstract class SomeAbstractClass {
12+
someMethod1: (param1: string, param2?: string) => string;
13+
}
14+
15+
export class SomeClass
16+
extends SomeAbstractClass
17+
implements SomeType1, SomeType2
18+
{
19+
someProperty1: string;
20+
someProperty2: string;
21+
someProperty3: string;
22+
}
23+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
index.ts(30,83): error TS2339: Property 'someProperty1' does not exist on type 'SomeClass'.
2+
index.ts(33,83): error TS2339: Property 'someProperty2' does not exist on type 'SomeClass'.
3+
index.ts(36,83): error TS2339: Property 'someProperty3' does not exist on type 'SomeClass'.
4+
index.ts(39,9): error TS2741: Property 'someMethod1' is missing in type 'SomeClass' but required in type 'SomeAbstractClass'.
5+
index.ts(40,9): error TS2741: Property 'someProperty1' is missing in type 'SomeClass' but required in type 'SomeType1'.
6+
index.ts(41,9): error TS2739: Type 'SomeClass' is missing the following properties from type 'SomeType2': someProperty2, someProperty3
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@aws-amplify/class-break-inheritance",
3+
"type": "module",
4+
"exports": {
5+
".": {
6+
"import": "./lib/index.js",
7+
"require": "./lib/index.js",
8+
"types": "./lib/index.d.ts"
9+
}
10+
},
11+
"types": "lib/index.d.ts"
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export type SomeType1 = {
2+
someProperty1: string;
3+
};
4+
5+
export type SomeType2 = {
6+
someProperty2: string;
7+
someProperty3: string;
8+
};
9+
10+
export abstract class SomeAbstractClass {
11+
someMethod1: (param1: string, param2?: string) => string;
12+
}
13+
14+
// remove all inheritance here
15+
export class SomeClass {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../../../../../../tsconfig.base.json",
3+
"compilerOptions": { "rootDir": "src", "outDir": "lib" }
4+
}

scripts/components/api-changes-validator/test-resources/test-projects/without-breaks/project-without-breaks/API.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ export abstract class SomeAbstractClass2<T1, T2, T3, T4, T5, T6> {
4747
constructor(param1: T1, param2?: T2);
4848
}
4949

50+
export class SomeDerivedClass1
51+
extends SomeAbstractClass1
52+
implements SampleType {}
53+
54+
export class SomeDerivedClass2<T1, T2 extends SampleType, T3, T4, T5, T6>
55+
extends SomeAbstractClass2<T1, T2, T3, T4, T5, T6>
56+
implements SampleTypeWithTypeParam<T1, T2>
57+
{
58+
anotherProperty: T2;
59+
someProperty: T1;
60+
}
61+
5062
export const someFunction1: () => void;
5163
export const someFunction2: (param1: string, param2?: number) => string;
5264
export const someFunction3: (param1: string, param2: number = 1) => string;

scripts/components/api-changes-validator/test-resources/test-projects/without-breaks/project-without-breaks/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ export abstract class SomeAbstractClass2<T1, T2, T3, T4, T5, T6> {
6464
}
6565
}
6666

67+
export class SomeDerivedClass1
68+
extends SomeAbstractClass1
69+
implements SampleType {}
70+
71+
export class SomeDerivedClass2<T1, T2 extends SampleType, T3, T4, T5, T6>
72+
extends SomeAbstractClass2<T1, T2, T3, T4, T5, T6>
73+
implements SampleTypeWithTypeParam<T1, T2>
74+
{
75+
anotherProperty: T2;
76+
someProperty: T1;
77+
}
78+
6779
export const someFunction1 = (): void => {
6880
throw new Error();
6981
};

0 commit comments

Comments
 (0)