Skip to content

Commit f474ad4

Browse files
author
Kamil Sobol
authored
add class constructor coverage to api check (#867)
* add class constructor coverage to api check * pr feedback
1 parent 74846bd commit f474ad4

File tree

9 files changed

+264
-19
lines changed

9 files changed

+264
-19
lines changed

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

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,20 @@ export class SomeClass5 {
130130
export class SomeClass6 {
131131
static someStaticProperty: string;
132132
}
133-
export class SomeClass7<T1, T2, T3, T4, T5, T6> {
133+
export class SomeClass7<T1 extends SomeClass1, T2, T3, T4, T5, T6> {
134134
constructor(param1: T1, param2?: T2);
135135
someMethod: (param1: T3, param2?: T4) => T5;
136136
someProperty: T6;
137137
}
138-
export abstract class SomeAbstractClass {
138+
export abstract class SomeAbstractClass1 {
139139
constructor(param1: string, param2?: string);
140140
someMethod: (param1: string, param2?: string) => string;
141141
someProperty: string;
142142
static someStaticMethod: (param1: string, param2?: string) => string;
143143
static someStaticProperty: string;
144+
}
145+
export abstract class SomeAbstractClass2<T1 extends SomeClass1, T2, T3, T4, T5, T6> {
146+
constructor(param1: T1, param2?: T2);
144147
}
145148
`,
146149
expectedApiUsage: `
@@ -151,7 +154,14 @@ import { SomeClass4 } from 'samplePackageName';
151154
import { SomeClass5 } from 'samplePackageName';
152155
import { SomeClass6 } from 'samplePackageName';
153156
import { SomeClass7 } from 'samplePackageName';
154-
import { SomeAbstractClass } from 'samplePackageName';
157+
import { SomeAbstractClass1 } from 'samplePackageName';
158+
import { SomeAbstractClass2 } from 'samplePackageName';
159+
160+
const someClass2ConstructorUsageFunction = (param1: string, param2?: string) => {
161+
new SomeClass2(param1);
162+
new SomeClass2(param1, param2);
163+
}
164+
155165
156166
const someClass3SomeMethodUsageOuterFunction = (someClass3SomeMethodUsageOuterFunctionParameter: SomeClass3) => {
157167
const SomeClass3SomeMethodUsageInnerFunction = (param1: string, param2?: string) => {
@@ -182,34 +192,50 @@ const someClass6SomeStaticPropertyUsageOuterFunction = (someClass6SomeStaticProp
182192
}
183193
184194
185-
const someClass7SomeMethodUsageOuterFunction = <T1, T2, T3, T4, T5, T6>(someClass7SomeMethodUsageOuterFunctionParameter: SomeClass7<T1, T2, T3, T4, T5, T6>) => {
195+
const someClass7ConstructorUsageFunction = <T1 extends SomeClass1, T2, T3, T4, T5, T6>(param1: T1, param2?: T2) => {
196+
new SomeClass7<T1, T2, T3, T4, T5, T6>(param1);
197+
new SomeClass7<T1, T2, T3, T4, T5, T6>(param1, param2);
198+
}
199+
const someClass7SomeMethodUsageOuterFunction = <T1 extends SomeClass1, T2, T3, T4, T5, T6>(someClass7SomeMethodUsageOuterFunctionParameter: SomeClass7<T1, T2, T3, T4, T5, T6>) => {
186200
const SomeClass7SomeMethodUsageInnerFunction = (param1: T3, param2?: T4) => {
187201
const returnValue: T5 = someClass7SomeMethodUsageOuterFunctionParameter.someMethod(param1);
188202
someClass7SomeMethodUsageOuterFunctionParameter.someMethod(param1, param2);
189203
}
190204
}
191-
const someClass7SomePropertyUsageOuterFunction = <T1, T2, T3, T4, T5, T6>(someClass7SomePropertyUsageOuterFunctionParameter: SomeClass7<T1, T2, T3, T4, T5, T6>) => {
205+
const someClass7SomePropertyUsageOuterFunction = <T1 extends SomeClass1, T2, T3, T4, T5, T6>(someClass7SomePropertyUsageOuterFunctionParameter: SomeClass7<T1, T2, T3, T4, T5, T6>) => {
192206
const propertyValue: T6 = someClass7SomePropertyUsageOuterFunctionParameter.someProperty;
193207
}
194208
195209
196-
const someAbstractClassSomeMethodUsageOuterFunction = (someAbstractClassSomeMethodUsageOuterFunctionParameter: SomeAbstractClass) => {
197-
const SomeAbstractClassSomeMethodUsageInnerFunction = (param1: string, param2?: string) => {
198-
const returnValue: string = someAbstractClassSomeMethodUsageOuterFunctionParameter.someMethod(param1);
199-
someAbstractClassSomeMethodUsageOuterFunctionParameter.someMethod(param1, param2);
210+
class SomeAbstractClass1DerivedUsageClass extends SomeAbstractClass1{
211+
constructor(param1: string, param2?: string) {
212+
super(param1, param2);
200213
}
201214
}
202-
const someAbstractClassSomePropertyUsageOuterFunction = (someAbstractClassSomePropertyUsageOuterFunctionParameter: SomeAbstractClass) => {
203-
const propertyValue: string = someAbstractClassSomePropertyUsageOuterFunctionParameter.someProperty;
215+
const someAbstractClass1SomeMethodUsageOuterFunction = (someAbstractClass1SomeMethodUsageOuterFunctionParameter: SomeAbstractClass1) => {
216+
const SomeAbstractClass1SomeMethodUsageInnerFunction = (param1: string, param2?: string) => {
217+
const returnValue: string = someAbstractClass1SomeMethodUsageOuterFunctionParameter.someMethod(param1);
218+
someAbstractClass1SomeMethodUsageOuterFunctionParameter.someMethod(param1, param2);
219+
}
220+
}
221+
const someAbstractClass1SomePropertyUsageOuterFunction = (someAbstractClass1SomePropertyUsageOuterFunctionParameter: SomeAbstractClass1) => {
222+
const propertyValue: string = someAbstractClass1SomePropertyUsageOuterFunctionParameter.someProperty;
204223
}
205-
const someAbstractClassSomeStaticMethodUsageOuterFunction = (someAbstractClassSomeStaticMethodUsageOuterFunctionParameter: SomeAbstractClass) => {
206-
const SomeAbstractClassSomeStaticMethodUsageInnerFunction = (param1: string, param2?: string) => {
207-
const returnValue: string = SomeAbstractClass.someStaticMethod(param1);
208-
SomeAbstractClass.someStaticMethod(param1, param2);
224+
const someAbstractClass1SomeStaticMethodUsageOuterFunction = (someAbstractClass1SomeStaticMethodUsageOuterFunctionParameter: SomeAbstractClass1) => {
225+
const SomeAbstractClass1SomeStaticMethodUsageInnerFunction = (param1: string, param2?: string) => {
226+
const returnValue: string = SomeAbstractClass1.someStaticMethod(param1);
227+
SomeAbstractClass1.someStaticMethod(param1, param2);
209228
}
210229
}
211-
const someAbstractClassSomeStaticPropertyUsageOuterFunction = (someAbstractClassSomeStaticPropertyUsageOuterFunctionParameter: SomeAbstractClass) => {
212-
const propertyValue: string = SomeAbstractClass.someStaticProperty;
230+
const someAbstractClass1SomeStaticPropertyUsageOuterFunction = (someAbstractClass1SomeStaticPropertyUsageOuterFunctionParameter: SomeAbstractClass1) => {
231+
const propertyValue: string = SomeAbstractClass1.someStaticProperty;
232+
}
233+
234+
235+
class SomeAbstractClass2DerivedUsageClass<T1 extends SomeClass1, T2, T3, T4, T5, T6> extends SomeAbstractClass2<T1, T2, T3, T4, T5, T6>{
236+
constructor(param1: T1, param2?: T2) {
237+
super(param1, param2);
238+
}
213239
}
214240
`,
215241
},

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

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ export class ClassUsageStatementsGenerator implements UsageStatementsGenerator {
216216
classMember as ts.PropertyDeclaration
217217
).generate().usageStatement ?? '';
218218
break;
219+
case ts.SyntaxKind.Constructor:
220+
usageStatement +=
221+
new ClassConstructorUsageStatementsGenerator(
222+
this.classDeclaration,
223+
classMember as ts.ConstructorDeclaration
224+
).generate().usageStatement ?? '';
225+
break;
219226
default:
220227
console.log(
221228
`Warning: class usage generator encountered unrecognized member kind ${classMember.kind}`
@@ -301,6 +308,113 @@ class ClassPropertyUsageStatementsGenerator
301308
};
302309
}
303310

311+
/**
312+
* Generates usage snippets for class constructor.
313+
*/
314+
class ClassConstructorUsageStatementsGenerator
315+
implements UsageStatementsGenerator
316+
{
317+
constructor(
318+
private readonly classDeclaration: ts.ClassDeclaration,
319+
private readonly constructorDeclaration: ts.ConstructorDeclaration
320+
) {}
321+
322+
generate = (): UsageStatements => {
323+
const isAbstract = this.classDeclaration.modifiers?.find(
324+
(modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword
325+
);
326+
if (isAbstract) {
327+
return this.generateAbstractClassConstructorUsage();
328+
}
329+
return this.generateConcreteClassConstructorUsage();
330+
};
331+
332+
/**
333+
* Generates usage patterns of concrete class constructor.
334+
* Generated snippets attempt to invoke constructor with min and max parameters.
335+
* Enclosing usage function is used to deliver parameters for constructor call.
336+
*/
337+
private generateConcreteClassConstructorUsage = (): UsageStatements => {
338+
const className = this.classDeclaration.name?.getText();
339+
if (!className) {
340+
throw new Error('Class name is missing');
341+
}
342+
const usageFunctionName = toLowerCamelCase(
343+
`${className}ConstructorUsageFunction`
344+
);
345+
const usageFunctionParameterDeclaration =
346+
new CallableParameterDeclarationUsageStatementsGenerator(
347+
this.constructorDeclaration.parameters
348+
).generate().usageStatement ?? '';
349+
const usageFunctionGenericParametersDeclaration =
350+
new GenericTypeParameterDeclarationUsageStatementsGenerator(
351+
this.classDeclaration.typeParameters
352+
).generate().usageStatement ?? '';
353+
const minParameterUsage =
354+
new CallableParameterUsageStatementsGenerator(
355+
this.constructorDeclaration.parameters,
356+
'min'
357+
).generate().usageStatement ?? '';
358+
const maxParameterUsage =
359+
new CallableParameterUsageStatementsGenerator(
360+
this.constructorDeclaration.parameters,
361+
'max'
362+
).generate().usageStatement ?? '';
363+
const genericTypeParameters =
364+
new GenericTypeParameterUsageStatementsGenerator(
365+
this.classDeclaration.typeParameters
366+
).generate().usageStatement ?? '';
367+
const callableSymbol = `new ${className}${genericTypeParameters}`;
368+
const minParameterCallWithReturnValue = `${callableSymbol}(${minParameterUsage});`;
369+
const maxParameterCall = `${callableSymbol}(${maxParameterUsage});`;
370+
371+
let usageStatement = `const ${usageFunctionName} = ${usageFunctionGenericParametersDeclaration}(${usageFunctionParameterDeclaration}) => {${EOL}`;
372+
usageStatement += `${indent(minParameterCallWithReturnValue)}${EOL}`;
373+
usageStatement += `${indent(maxParameterCall)}${EOL}`;
374+
usageStatement += `}${EOL}`;
375+
return { usageStatement };
376+
};
377+
378+
/**
379+
* Generates usage snippets for abstract class constructor.
380+
* Generated snippets have an attempt to derive from abstract class
381+
* and call it's constructor via super() call with matching list of parameters.
382+
*/
383+
private generateAbstractClassConstructorUsage = (): UsageStatements => {
384+
const className = this.classDeclaration.name?.getText();
385+
if (!className) {
386+
throw new Error('Class name is missing');
387+
}
388+
const derivedClassConstructorParameterUsage =
389+
new CallableParameterUsageStatementsGenerator(
390+
this.constructorDeclaration.parameters,
391+
// exploring just max is fine as abstract and derived class ctor
392+
// signatures match
393+
'max'
394+
).generate().usageStatement ?? '';
395+
const derivedClassGenericParametersDeclaration =
396+
new GenericTypeParameterDeclarationUsageStatementsGenerator(
397+
this.classDeclaration.typeParameters
398+
).generate().usageStatement ?? '';
399+
const genericTypeParameters =
400+
new GenericTypeParameterUsageStatementsGenerator(
401+
this.classDeclaration.typeParameters
402+
).generate().usageStatement ?? '';
403+
const derivedClassName = `${className}DerivedUsageClass`;
404+
const constructorDeclaration = this.constructorDeclaration
405+
.getText()
406+
// Strip trailing ';' as we want to define body.
407+
.replace(';', '');
408+
const superConstructorCall = `super(${derivedClassConstructorParameterUsage});`;
409+
let usageStatement = `class ${derivedClassName}${derivedClassGenericParametersDeclaration} extends ${className}${genericTypeParameters}{${EOL}`;
410+
usageStatement += `${indent(constructorDeclaration)} {${EOL}`;
411+
usageStatement += `${indent(indent(superConstructorCall))}${EOL}`;
412+
usageStatement += `${indent('}')}${EOL}`;
413+
usageStatement += `}${EOL}`;
414+
return { usageStatement };
415+
};
416+
}
417+
304418
/**
305419
* Generates usage of a variable/const/arrow function declared as top level export
306420
* , i.e. 'const someConst = ...`;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
```ts
2+
export class SomeClass1 {
3+
constructor(param1: string, param2?: string);
4+
}
5+
6+
export class SomeClass2 {
7+
constructor(param1: string, param2?: string);
8+
}
9+
10+
export class SomeClass3<T1, T2> {
11+
constructor(param1: T1, param2?: T2);
12+
}
13+
14+
export class SomeClass4<T1 extends SomeClass1, T2> {
15+
constructor(param1: T1, param2?: T2);
16+
}
17+
18+
export class SomeAbstractClass1 {
19+
constructor(param1: string, param2?: string);
20+
}
21+
22+
export class SomeAbstractClass2 {
23+
constructor(param1: string, param2?: string);
24+
}
25+
26+
export class SomeAbstractClass3<T1, T2> {
27+
constructor(param1: T1, param2?: T2);
28+
}
29+
30+
export class SomeAbstractClass4<T1 extends SomeClass1, T2> {
31+
constructor(param1: T1, param2?: T2);
32+
}
33+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
index.ts(12,26): error TS2554: Expected 1 arguments, but got 2.
2+
index.ts(18,26): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'number | undefined'.
3+
Type 'string' is not assignable to type 'number'.
4+
index.ts(23,18): error TS2558: Expected 1 type arguments, but got 2.
5+
index.ts(24,18): error TS2558: Expected 1 type arguments, but got 2.
6+
index.ts(36,34): error TS2554: Expected 1 arguments, but got 2.
7+
index.ts(42,34): error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'number | undefined'.
8+
index.ts(47,26): error TS2558: Expected 1 type arguments, but got 2.
9+
index.ts(48,26): error TS2558: Expected 1 type arguments, but got 2.
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-constructor",
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export class SomeClass1 {
2+
// remove parameter
3+
constructor(param1: string) {}
4+
}
5+
6+
export class SomeClass2 {
7+
// change parameter type
8+
constructor(param1: string, param2?: number) {}
9+
}
10+
11+
export class SomeClass3<T1> {
12+
// remove template type
13+
constructor(param1: T1, param2?: string) {}
14+
}
15+
16+
export class SomeClass4<T1 extends SomeClass2, T2> {
17+
// change template type
18+
constructor(param1: T1, param2?: T2) {}
19+
}
20+
21+
export class SomeAbstractClass1 {
22+
// remove parameter
23+
constructor(param1: string) {}
24+
}
25+
26+
export class SomeAbstractClass2 {
27+
// change parameter type
28+
constructor(param1: string, param2?: number) {}
29+
}
30+
31+
export class SomeAbstractClass3<T1> {
32+
// remove template type
33+
constructor(param1: T1, param2?: string) {}
34+
}
35+
36+
export class SomeAbstractClass4<T1 extends SomeClass2, T2> {
37+
// change template type
38+
constructor(param1: T1, param2?: T2) {}
39+
}
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ export class SampleClass7<T1, T2, T3, T4, T5, T6> {
3636
someMethod: (param1: T3, param2?: T4) => T5;
3737
someProperty: T6;
3838
}
39-
export abstract class SomeAbstractClass {
39+
export abstract class SomeAbstractClass1 {
4040
constructor(param1: string, param2?: string);
4141
someMethod: (param1: string, param2?: string) => string;
4242
someProperty: string;
4343
static someStaticMethod: (param1: string, param2?: string) => string;
4444
static someStaticProperty: string;
4545
}
46+
export abstract class SomeAbstractClass2<T1, T2, T3, T4, T5, T6> {
47+
constructor(param1: T1, param2?: T2);
48+
}
4649

4750
export const someFunction1: () => void;
4851
export const someFunction2: (param1: string, param2?: number) => string;

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class SampleClass7<T1, T2, T3, T4, T5, T6> {
4545
};
4646
someProperty: T6;
4747
}
48-
export abstract class SomeAbstractClass {
48+
export abstract class SomeAbstractClass1 {
4949
protected constructor(param1: string, param2?: string) {
5050
throw new Error();
5151
}
@@ -58,6 +58,11 @@ export abstract class SomeAbstractClass {
5858
};
5959
static someStaticProperty: string;
6060
}
61+
export abstract class SomeAbstractClass2<T1, T2, T3, T4, T5, T6> {
62+
protected constructor(param1: T1, param2?: T2) {
63+
throw new Error();
64+
}
65+
}
6166

6267
export const someFunction1 = (): void => {
6368
throw new Error();

0 commit comments

Comments
 (0)